Skip to content
This repository was archived by the owner on Apr 8, 2025. It is now read-only.

Commit 58408d1

Browse files
committed
Make it less ugly
1 parent f36ee3d commit 58408d1

File tree

5 files changed

+111
-26
lines changed

5 files changed

+111
-26
lines changed

docs/configuration.rst

+8
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,11 @@ You can customize these configuration options in your ``conf.py`` file:
1616
Default: ``'minified'``
1717

1818
Type: ``string``
19+
20+
.. confval:: rtd_sphinx_search_filters
21+
22+
Description: List of filters to show in the search bar.
23+
24+
Default: ``{"default": "project:@this"}``
25+
26+
Type: ``dict``

sphinx_search/extension.py

+13
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,16 @@ def _get_static_files(config):
2626

2727

2828
def get_context(config):
29+
"""
30+
Get context for templates.
31+
32+
This mainly returns the settings from the extension
33+
that are needed in our JS code.
34+
"""
2935
filters = config.rtd_sphinx_search_filters.copy()
3036
default_filter = filters.pop("default", "")
3137
# When converting to JSON, the order of the keys is not guaranteed.
38+
# So we pass a list of tuples to preserve the order.
3239
filters = [(name, filter) for name, filter in filters.items()]
3340
return {
3441
"rtd_search_config": {
@@ -39,6 +46,11 @@ def get_context(config):
3946

4047

4148
def copy_asset_files(app, exception):
49+
"""
50+
Copy assets files to the output directory.
51+
52+
If the name of the file ends with ``_t``, it will be interpreted as a template.
53+
"""
4254
if exception is None: # build succeeded
4355
root = Path(__file__).parent
4456
for file in _get_static_files(app.config):
@@ -56,6 +68,7 @@ def inject_static_files(app):
5668
"""Inject correct CSS and JS files based on the value of ``rtd_sphinx_search_file_type``."""
5769
for file in _get_static_files(app.config):
5870
file = str(file)
71+
# Templates end with `_t`, Sphinx removes the _t when copying the file.
5972
if file.endswith('_t'):
6073
file = file[:-2]
6174
if file.endswith('.js'):

sphinx_search/static/css/rtd_sphinx_search.css

+27-3
Original file line numberDiff line numberDiff line change
@@ -149,9 +149,12 @@
149149

150150
/* Search result */
151151

152+
.search__result__box {
153+
padding: 0px 10px;
154+
}
155+
152156
.search__result__single {
153157
margin-top: 10px;
154-
padding: 0px 10px;
155158
border-bottom: 1px solid #e6e6e6;
156159
}
157160

@@ -282,17 +285,38 @@
282285
letter-spacing: 1px;
283286
}
284287

288+
.search__filters {
289+
padding: 0px 10px;
290+
}
291+
285292
.search__filters ul {
286293
list-style: none;
287294
padding: 0;
288295
margin: 0;
289296
}
290297

291298
.search__filters li {
292-
display: inline;
293-
margin-right: 0.5em;
299+
display: inline;
300+
margin-right: 5px;
301+
}
302+
303+
.search__filters .search__filters__title {
304+
color: black;
305+
font-size: 15px;
306+
}
307+
308+
.search__filters button {
309+
background: #f3f4f5;
310+
border-color: #d0d7de;
311+
color: black;
312+
border-style: solid;
313+
border-width: 1px;
314+
border-radius: 999px;
315+
padding: 2px 7px;
316+
font-size: 15px;
294317
}
295318

319+
296320
@media (max-width: 670px) {
297321
.rtd__search__credits {
298322
height: 50px;
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
{# Set the extension options as a JSON object, so it can be used from our JS code. #}
12
var RTD_SEARCH_CONFIG = {{ rtd_search_config | tojson }};

sphinx_search/static/js/rtd_sphinx_search.js

+62-23
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@ const FETCH_RESULTS_DELAY = 250;
66
const CLEAR_RESULTS_DELAY = 300;
77
const RTD_SEARCH_PARAMETER = "rtd_search";
88

9+
/**
10+
* Parse the search query and return an array with the query and the options.
11+
*
12+
* This is a simplified version of our parser
13+
* https://github.com/readthedocs/readthedocs.org/blob/main/readthedocs/search/api/v3/queryparser.py.
14+
* All `foo:bar` terms are considered options, and the rest are considered part of the query.
15+
*
16+
* @param {String} search_query
17+
* @return {Array} [query, options]
18+
*/
919
function parseQuery(search_query) {
1020
let query = [];
1121
let options = {};
@@ -131,6 +141,11 @@ const updateSearchBar = () => {
131141
};
132142

133143

144+
/**
145+
* Set the search query in the modal.
146+
*
147+
* @param {String} query
148+
*/
134149
function setSearchQuery(query) {
135150
let search_outer_input = document.querySelector(".search__outer__input");
136151
search_outer_input.value = query;
@@ -297,13 +312,16 @@ const generateSingleResult = (resultData, projectName, id) => {
297312
let h2_element = createDomNode("h2", {class: "search__result__title"});
298313
h2_element.innerHTML = page_title;
299314

300-
// If the result is not from the same project,
301-
// then it's from a subproject.
315+
// Results can belong to different projects.
316+
// If the result isn't from the current project, add a note about it.
302317
const project_slug = resultData.project.slug
303318
if (projectName !== project_slug) {
304319
let subtitle = createDomNode("small", {class: "rtd_ui_search_subtitle"});
305320
subtitle.innerText = ` (from project ${project_slug})`;
306321
h2_element.appendChild(subtitle);
322+
// If the result isn't from the current project,
323+
// then we create an absolute link to the page.
324+
page_link = `${resultData.domain}${page_link}`;
307325
}
308326
h2_element.appendChild(createDomNode("br"))
309327

@@ -577,7 +595,8 @@ const fetchAndGenerateResults = (api_endpoint, parameters) => {
577595
* This html structure will serve as the boilerplate
578596
* to show our search results.
579597
*
580-
* @param {Array} filters: filters to be applied to the search
598+
* @param {Array} filters: filters to be applied to the search.
599+
* {["Filter name", "Filter value"]}
581600
* @return {String} initial html structure
582601
*/
583602
const generateAndReturnInitialHtml = (filters) => {
@@ -597,35 +616,53 @@ const generateAndReturnInitialHtml = (filters) => {
597616
</div>
598617
</div>
599618
<div class="rtd__search__credits">
600-
Search by <a href="https://readthedocs.org/">Read the Docs</a> & <a href="https://readthedocs-sphinx-search.readthedocs.io/en/latest/">readthedocs-sphinx-search</a>
619+
Search by <a href="https://readthedocs.org/">Read the Docs</a> & <a href="https://readthedocs-sphinx-search.readthedocs.io/en/latest/">readthedocs-sphinx-search</a>.
620+
<a href="https://docs.readthedocs.io/page/server-side-search/syntax.html">Search syntax</a>.
601621
</div>
602622
`;
603623

604624
let div = createDomNode("div", {
605625
class: "search__outer__wrapper search__backdrop",
606626
});
607627
div.innerHTML = innerHTML;
628+
608629
let filters_list = div.querySelector(".search__filters ul");
609630
const config = getConfig();
610-
for (const [filter, value] of filters) {
611-
let li = createDomNode("li");
612-
let button = createDomNode("button");
613-
button.innerText = filter;
614-
button.value = value;
615-
button.addEventListener("click", event => {
616-
event.preventDefault();
617-
let search_query = getSearchTerm();
618-
let [query, _] = parseQuery(search_query);
619-
setSearchQuery(event.target.value + " " + query);
620-
const search_params = {
621-
q: search_query,
622-
project: config.project,
623-
version: config.version,
624-
};
625-
fetchAndGenerateResults(config.api_endpoint, search_params)();
626-
});
627-
li.appendChild(button);
628-
filters_list.appendChild(li);
631+
// Add filters below the search box if present.
632+
if (filters.length > 0) {
633+
let li = createDomNode("li", {"class": "search__filters__title"});
634+
li.innerText = "Filters:";
635+
filters_list.appendChild(li);
636+
}
637+
// Each filter is a button in the filters list.
638+
// Each button contains the index of the filter,
639+
// so we can get the proper filter when clicked.
640+
for (let i = 0, len = filters.length; i < len; i++) {
641+
const [name, filter] = filters[i];
642+
let li = createDomNode("li");
643+
let button = createDomNode("button");
644+
button.innerText = name;
645+
button.value = i;
646+
button.title = filter;
647+
button.addEventListener("click", event => {
648+
event.preventDefault();
649+
// To apply a filter, we extract the current query and
650+
// replace the current options with the selected filter.
651+
let filter = filters[parseInt(event.target.value)][1];
652+
let search_query = getSearchTerm();
653+
let [query, _] = parseQuery(search_query);
654+
search_query = filter + " " + query;
655+
setSearchQuery(search_query);
656+
// Perform the search with the new query.
657+
const search_params = {
658+
q: search_query,
659+
project: config.project,
660+
version: config.version,
661+
};
662+
fetchAndGenerateResults(config.api_endpoint, search_params)();
663+
});
664+
li.appendChild(button);
665+
filters_list.appendChild(li);
629666
}
630667
return div;
631668
};
@@ -767,6 +804,8 @@ window.addEventListener("DOMContentLoaded", () => {
767804
// cancel previous ajax request.
768805
current_request.cancel();
769806
}
807+
// If the query doesn't have options,
808+
// use the default filter.
770809
let [query, options] = parseQuery(search_query);
771810
if (Object.keys(options).length == 0) {
772811
search_query = config.default_filter + " " + query;

0 commit comments

Comments
 (0)