Skip to content

Commit 3961b64

Browse files
sloweCadairpre-commit-ci[bot]nabobalis
authored
Adds enhanced search (#287)
* Add enhanced search * Include search js in sphinx build * Use /_/ proxy on RTD * Use /_/ proxy on RTD * Second attempt at using _ RTD api * Fix bug with keyboard navigation on project tabs * Add some default classes * Tidy up code A more compact version of the URL building. Remove/define some variables. * Change button style Switching to classes `sd-btn sd-bg-primary sd-bg-text-primary` from https://sunpy--287.org.readthedocs.build/projects/sunpy-sphinx-theme/287/web-components.html * Add filter icon * Add Python side config * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Extract search CSS and re-org some files * Fix some search css * Add an editorconfig file because indentation * More dark theme fixes * Keep the linter happy * Fix a config bug * Add documentation --------- Co-authored-by: Stuart Mumford <stuart@cadair.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Nabil Freij <nabil.freij@gmail.com>
1 parent ad15848 commit 3961b64

8 files changed

Lines changed: 839 additions & 10 deletions

File tree

docs/customizing.rst

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
.. _customizing:
2+
13
Customizing the Theme
24
=====================
35

@@ -9,6 +11,9 @@ Changing the Theme Settings
911
As well as all the default configuration settings we inherit from the pydata-sphinx-theme the sunpy-sphinx-theme provides the following configuration options.
1012
All of these options should be set inside the ``html_theme_options`` dictionary in your ``conf.py`` file.
1113

14+
General Configuration
15+
^^^^^^^^^^^^^^^^^^^^^
16+
1217
``footer_center``
1318
#################
1419

@@ -50,6 +55,67 @@ It's possible to add dropdown menus to the topnav by setting document equal to a
5055
Footer links are a number of links which will get placed in the center of the footer.
5156
They are specified in the same format as the ``navbar_links``.
5257

58+
Analytics with Goat Counter
59+
^^^^^^^^^^^^^^^^^^^^^^^^^^^
60+
61+
The theme supports using `Goat Counter <https://www.goatcounter.com/>`__ to monitor traffic. Goat Counter is an Open Source project with free hosting, so a good fit for many projects using this theme.
62+
GoatCounter support is enabled by default for the ``sunpy.org`` domain, so if your ``sst_site_root="https://sunpy.org"`` then it will be enabled with the default SunPy settings.
63+
To enable it for other sites you need to do configure the following options.
64+
65+
``goatcounter_analytics_url``
66+
#############################
67+
68+
This is the analytics URL for the GoatCounter project, for example ``"https://sunpy.goatcounter.com/count"``.
69+
70+
``goatcounter_non_domain_endpoint``
71+
###################################
72+
73+
This setting configures how analytics work for domains which are not ``sst_site_root`` or subdomains of the root.
74+
The default is ``False`` meaning that if the domain isn't ``sst_site_root`` then GoatCounter is disabled.
75+
If you set this to a GoatCounter endpoint (such as ``goatcounter_analytics_endpoint`` then that endpoint will be used for domains which are not ``sst_site_root``.
76+
77+
78+
Read the Docs Search
79+
^^^^^^^^^^^^^^^^^^^^
80+
81+
The theme supports an enhanced version of the `Server Side Search <https://docs.readthedocs.com/platform/stable/server-side-search/index.html>`__ Read the Docs add-on.
82+
This adds a similar UI, but enables searching multiple Read the Docs projects simultaneously, not just the current RTD project and all subprojects as supported by the official add-on.
83+
This means that when configured this enables all RTD projects using the theme to search all others, i.e. if you have a parent project "sunpy" and a subproject "ndcube" this search allows users in the "ndcube" documentation to also search "sunpy", whereas the official one only allows users in "sunpy" to also search "ndcube".
84+
85+
This is enabled by default, but will not work unless accessing the documentation via Read the Docs, if the enhanced search fails for any reason it will fallback to the standard sphinx search.
86+
To facilitate debugging of this extension, it supports proxying requests to the RTD search API through the `corsproxy.io <https://corsproxy.io>`__ service if the ``?debug`` query parameter is set on the URL, set this if testing the search on a local build.
87+
88+
``rtd_search``
89+
##############
90+
91+
Enable or disable the RTD search functionality. Defaults to ``True``.
92+
93+
``rtd_search_projects``
94+
#######################
95+
96+
This configuration option is the list of projects that the search will query the RTD API for results.
97+
By default this configuration uses all the links in ``navbar_links``.
98+
The format of this option is a list of dictionaries with ``"name"`` and ``"links"`` keys, i.e.:
99+
100+
.. code-block:: python
101+
102+
rtd_search_projects = [
103+
{"name": "sunpy", "link": "https://docs.sunpy.org"}
104+
]
105+
106+
107+
``rtd_search_load_more_label``
108+
##############################
109+
110+
This is the label shown on the button to load more results from the RTD API.
111+
The default is ``"Load more results"``.
112+
113+
``rtd_search_no_results_label``
114+
###############################
115+
116+
This is the label shown when no results match the query.
117+
The default is ``"There are no results for this search"``.
118+
53119
Adjusting the Styling
54120
---------------------
55121

docs/index.rst

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,22 @@ sunpy-sphinx-theme
44
The ``sunpy-sphinx-theme`` is a `sphinx` theme designed for the needs of the SunPy Project's main website and subproject documentation pages.
55
It's primary goals are:
66

7-
* To provide a consistent look and feel over all the sunpy.org websites and subdomains.
8-
* To give all documentation pages maintained by the SunPy project a branded appearance.
7+
* To provide a consistent look and feel over all the separate documentation sites for the project.
8+
* To give all documentation pages maintained a consistent branded appearance.
99
* To provide a global navigation bar over all these sites so that it is possible to navigate between the sunpy.org site and all the documentation.
1010

1111
Version 2 of this package achieves this by being a sub-theme of the `pydata-sphinx-theme` with a heavily modified header bar.
1212
The core changes between this theme and the `pydata-sphinx-theme` are:
1313

1414
* Remove the toctree navigation from the header bar and place it all in the sidebar (in the same manner as sphinx-book-theme).
15-
* Add links to the header bar which are specified as theme config variables, with defaults for sunpy.org.
15+
* Add links to the header bar which are specified as theme config variables, so they are the same over all sites.
1616
* Add a center element to the footer bar.
1717
* Restyled the theme for SunPy colors.
1818
* Added optional support for analytics though `goatcounter.com <https://www.goatcounter.com/>`__ which defaults to on for sunpy.org domains.
19+
* Added optional support for a multi-project Read the Docs search interface.
20+
21+
The theme is highly configurable, although the defaults are for the SunPy project, this theme can be used for other organisations where they have multiple documentation sites and want a unified user experience.
22+
For information on how to configure this theme for your site see :ref:`customizing`.
1923

2024
.. grid:: 1 2 2 2
2125
:gutter: 3

src/sunpy_sphinx_theme/__init__.py

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
SunPy Sphinx Theme.
33
"""
44

5+
import json
56
import os
67
from functools import partial
78
from pathlib import Path
9+
from textwrap import dedent, indent
810
from urllib.parse import urljoin
911

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

156158

159+
def generate_search_config(app):
160+
"""
161+
This function parses the config for the "Documentation" section of the theme config.
162+
"""
163+
theme_config = utils.get_theme_options_dict(app)
164+
search_projects = theme_config.get("rtd_search_projects", None)
165+
if search_projects is None:
166+
navbar_links = theme_config["navbar_links"]
167+
doc_links = next(section[1] for section in navbar_links if section[0] == "Documentation")
168+
169+
def filter_doc_links(links):
170+
out_links = []
171+
for link in links:
172+
if isinstance(link[1], list):
173+
out_links += filter_doc_links(link[1])
174+
elif isinstance(link[1], str) and link[1].startswith("http"):
175+
out_links.append({"name": link[0], "link": link[1]})
176+
else:
177+
err = f"Unable to parse {link} in the nav tree. Try setting search_projects explicitly or fixing navbar_links."
178+
raise ValueError(err)
179+
return out_links
180+
181+
search_projects = filter_doc_links(doc_links)
182+
183+
load_more_label = theme_config.get("rtd_search_load_more_label", "Load more results")
184+
no_results_label = theme_config.get("rtd_search_no_results_label", "There are no results for this search")
185+
script = dedent(f"""
186+
const set_search_config = {{
187+
"no-results":{{
188+
"label": "{no_results_label}"
189+
}},
190+
"load-more":{{
191+
"label": "{load_more_label}",
192+
"class": "btn sd-btn sd-bg-primary sd-bg-text-primary"
193+
}},
194+
"projects":{indent(json.dumps(search_projects, indent=2), " " * 10, predicate=lambda line: line.strip() != "[")}
195+
}};
196+
""")
197+
app.add_js_file(None, body=script)
198+
199+
157200
def setup(app: Sphinx):
158201
# Register theme
159202
theme_dir = get_html_theme_path()
160203
app.add_html_theme("sunpy", theme_dir)
161204
app.add_css_file("sunpy_style.css", priority=600)
162-
app.connect("builder-inited", update_config)
205+
app.connect("builder-inited", update_config, priority=100)
206+
app.connect("builder-inited", generate_search_config, priority=500)
163207
app.connect("html-page-context", update_html_context)
164208
# Conditionally include goat counter js
165209
# We can't do this in update_config as that causes the scripts to be duplicated.
@@ -198,6 +242,14 @@ def setup(app: Sphinx):
198242
loading_method="async",
199243
)
200244

245+
if theme_options.get("rtd_search", True):
246+
# Add project-wide search
247+
app.add_css_file("css/rtd_enhanced_search.css")
248+
app.add_js_file(
249+
"js/rtd_enhanced_search.js",
250+
loading_method="async",
251+
)
252+
201253
return {
202254
"parallel_read_safe": True,
203255
"parallel_write_safe": True,

src/sunpy_sphinx_theme/cards.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ def run(self):
109109

110110

111111
def setup(app: Sphinx):
112-
app.add_css_file("sunpy_cards.css", priority=600)
112+
app.add_css_file("css/sunpy_cards.css", priority=600)
113113
app.add_directive("custom-card", Card)
114114
app.add_node(_Card, html=(visit_card_node, depart_card_node))
115115
return {
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
#pst-search-dialog[open] {
2+
left: revert;
3+
top: 2rem;
4+
margin-top: 0;
5+
transform: revert;
6+
max-height: calc(100vh - 4rem);
7+
}
8+
9+
#pst-search-dialog[open] form.bd-search {
10+
flex-grow: 0;
11+
}
12+
13+
.readthedocs-search {
14+
background: var(--pst-color-background);
15+
border-radius: 0.25rem;
16+
width: 100%;
17+
display: flex;
18+
flex-direction: column;
19+
overflow: hidden;
20+
}
21+
22+
.readthedocs-search form {
23+
margin: 0.5rem;
24+
border: 0;
25+
}
26+
27+
.readthedocs-search form:focus-within {
28+
outline: 2px solid var(--pst-color-accent);
29+
border: 0;
30+
}
31+
32+
/* .readthedocs-search form.loading input { */
33+
/* } */
34+
35+
.readthedocs-search form.loading .fa-magnifying-glass {
36+
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>");
37+
background-position: left center;
38+
background-repeat: no-repeat;
39+
background-size: contain;
40+
}
41+
42+
html[data-theme="dark"] .readthedocs-search form.loading .fa-magnifying-glass {
43+
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>");
44+
}
45+
46+
.readthedocs-search form.loading .fa-magnifying-glass path {
47+
display: none;
48+
}
49+
50+
.readthedocs-search .content {
51+
max-height: calc(100% - 110px);
52+
overflow-y: auto;
53+
}
54+
55+
.readthedocs-search .tablist {
56+
position: sticky;
57+
display: flex;
58+
overflow-x: auto;
59+
top: 0;
60+
outline: 0;
61+
background: var(--pst-color-background);
62+
}
63+
64+
.readthedocs-search .tablist button {
65+
display: flex;
66+
border: 0;
67+
border-bottom: max(3px, 0.1875rem, 0.12em) solid transparent;
68+
color: var(--pst-color-text-base);
69+
background-color: var(--pst-color-surface);
70+
padding: 0.5rem 1rem;
71+
align-items: center;
72+
white-space: nowrap;
73+
}
74+
75+
.readthedocs-search .tablist button:focus {
76+
color: var(--sst-lightest-color);
77+
background-color: var(--sst-dark-color);
78+
}
79+
80+
.readthedocs-search .tablist button:focus,
81+
.readthedocs-search .tablist button[aria-selected="true"] {
82+
outline: 0;
83+
border-bottom: max(3px, 0.1875rem, 0.12em) solid var(--pst-color-secondary);
84+
}
85+
86+
.readthedocs-search .tablist button svg {
87+
padding-right: 0.25rem;
88+
}
89+
90+
.readthedocs-search .tablist button .n {
91+
padding-left: 0.25rem;
92+
}
93+
94+
.readthedocs-search .results .results-message > * {
95+
margin: 1rem;
96+
}
97+
98+
.readthedocs-search .results ul {
99+
list-style: none;
100+
padding: 0;
101+
margin: 0;
102+
}
103+
104+
.readthedocs-search .results ul li {
105+
margin: 0;
106+
padding: 1rem;
107+
}
108+
109+
.readthedocs-search .results ul li:focus {
110+
background: var(--pst-color-attention-bg);
111+
outline: 0;
112+
}
113+
114+
.readthedocs-search .results ul li:first-child {
115+
border: 0;
116+
}
117+
118+
/* .readthedocs-search .results ul li > *:last-child { */
119+
/* } */
120+
121+
.readthedocs-search .results ul li a:hover,
122+
.readthedocs-search .results ul li a:focus,
123+
.readthedocs-search .results ul li.selected a {
124+
color: var(--pst-color-text-base);
125+
background-color: var(--sst-footer-background-color);
126+
}
127+
128+
.readthedocs-search .footer {
129+
padding: 10px;
130+
border-top: 1px solid #e1e4e5;
131+
display: flex;
132+
gap: 1em;
133+
justify-content: space-between;
134+
align-items: center;
135+
font-size: 12px;
136+
color: var(--pst-color-text-base);
137+
background-color: var(--sst-footer-background-color);
138+
}
139+
140+
.readthedocs-search .help {
141+
display: flex;
142+
list-style: none;
143+
margin: 0;
144+
padding: 0;
145+
gap: 20px;
146+
}
147+
148+
.readthedocs-search .credits {
149+
display: flex;
150+
align-items: center;
151+
gap: 5px;
152+
margin-left: auto;
153+
text-align: right;
154+
}
155+
.readthedocs-search .credits a {
156+
color: inherit;
157+
}
158+
159+
.readthedocs-search .credits svg {
160+
height: 20px;
161+
}
162+
163+
@media only screen and (max-width: 70rem) {
164+
.readthedocs-search .footer,
165+
.readthedocs-search .help {
166+
display: block;
167+
}
168+
}

src/sunpy_sphinx_theme/theme/sunpy/static/sunpy_cards.css renamed to src/sunpy_sphinx_theme/theme/sunpy/static/css/sunpy_cards.css

File renamed without changes.

0 commit comments

Comments
 (0)