Skip to content
Merged
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
6 changes: 6 additions & 0 deletions sphinx_searchlite/static/engine/searchlite.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,12 @@
return [];
})
.then(function (records) {
// Record urls are relative to the documentation root, but a page
// nested below it would resolve them against its own directory.
var root = new URL("../", new URL(url, document.baseURI));
records.forEach(function (record) {
record.u = new URL(record.u, root).href;
});
index = build(records);
return index;
});
Expand Down
14 changes: 13 additions & 1 deletion sphinx_searchlite/static/ui/searchlite-ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,15 @@
seen.add(field);
field.classList.add("searchlite-adopted");
field.readOnly = true;
field.addEventListener("focus", open);
field.addEventListener("click", open);
// Opening on `focus` traps the user: closing the dialog restores focus
// to the field that opened it, which would immediately reopen it.
field.addEventListener("keydown", function (event) {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
open();
}
});
var form = field.closest("form");
if (form) {
form.addEventListener("submit", function (event) {
Expand Down Expand Up @@ -174,6 +181,11 @@
event.preventDefault();
window.location.href = active.href;
}
} else if (event.key === "Escape") {
// `<input type=search>` swallows the first Escape to clear itself, which
// would leave the dialog needing two presses despite the `Esc` hint.
event.preventDefault();
dialog.close();
}
});

Expand Down
19 changes: 19 additions & 0 deletions sphinx_searchlite/tests/test_searchlite.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,14 @@ def test_index_is_still_emitted(self, built):
assert (built / "_static" / "searchlite-index.json").is_file()


class TestResultLinks:
def test_urls_resolve_against_the_documentation_root(self, built):
js = (built / "_static" / "searchlite.js").read_text()
# Records store root-relative urls, so a page nested below the root
# would otherwise resolve them against its own directory.
assert 'new URL("../", new URL(url, document.baseURI))' in js


class TestThemeSearchAdoption:
def test_adoption_is_advertised_to_the_ui_script(self, built):
assert 'data-searchlite-adopt="true"' in (built / "guide.html").read_text()
Expand All @@ -125,6 +133,17 @@ def test_adoption_can_be_switched_off(self, tmp_path_factory):
out = _build(tmp_path_factory, CONF + "\nsearchlite_adopt_theme_search = False\n", "src_no_adopt")
assert 'data-searchlite-adopt="false"' in (out / "guide.html").read_text()

def test_adopted_fields_do_not_reopen_the_dialog_on_focus(self, built):
js = (built / "_static" / "searchlite-ui.js").read_text()
# Closing the dialog restores focus to the field that opened it, so an
# opener bound to `focus` would reopen it and trap the reader.
assert 'field.addEventListener("focus", open)' not in js

def test_escape_closes_the_dialog_on_the_first_press(self, built):
js = (built / "_static" / "searchlite-ui.js").read_text()
# `<input type=search>` eats the first Escape to clear itself.
assert 'event.key === "Escape"' in js

def test_styles_no_longer_hardcode_a_dark_palette(self, built):
css = (built / "_static" / "searchlite.css").read_text()
# Colours are adopted from the host page instead, since themes signal
Expand Down