doc: Make search engine configurable

Add UI to select preferred search engine when Google Programmable Search is
enabled. The user's preference is saved using local storage.

This also makes the search input field of type "search" for better UX (in
particular on mobile).

Signed-off-by: Benjamin Cabé <benjamin@zephyrproject.org>
This commit is contained in:
Benjamin Cabé 2023-11-07 11:32:08 +01:00 committed by Anas Nashif
parent 7688f4859c
commit 7e253ff937
5 changed files with 214 additions and 31 deletions

View File

@ -552,7 +552,10 @@ kbd, .kbd,
background-color: var(--navbar-background-color-active);
}
.wy-side-nav-search input[type="text"] {
.wy-side-nav-search input[type=search] {
width: 100%;
border-radius: 50px;
padding: 6px 12px;
background-color: var(--input-background-color);
color: var(--body-color);
/* Avoid reflowing when toggling the focus state */
@ -563,11 +566,11 @@ kbd, .kbd,
font-size: 14px;
}
.wy-side-nav-search input[type="text"]:focus {
.wy-side-nav-search input[type="search"]:focus {
border: 2px solid var(--input-focus-border-color);
}
.wy-side-nav-search input[type="text"]::placeholder {
.wy-side-nav-search input[type="search"]::placeholder {
color: var(--body-color);
opacity: 0.55;
}
@ -889,3 +892,61 @@ dark-mode-toggle::part(toggleLabel){
font-size: 3rem;
color: white;
}
/* Custom search box, including search engine selection */
.search-container {
position: relative;
}
#search-se-settings-icon {
position: absolute;
color: var(--body-color);
right: 10px;
top: 50%;
transform: translateY(-50%);
cursor: pointer;
opacity: 0.8;
}
#search-se-menu {
display: none;
position: absolute;
font-size: 11px;
background-color: var(--input-background-color);
color: var(--body-color);
right: 0px;
top: 36px;
border: solid 1px var(--body-color);
border-radius: 10px;
z-index: 1000;
}
#search-se-menu ul {
list-style: none;
margin: 0;
padding: 2px;
}
#search-se-menu ul li {
padding: 8px 12px;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
gap: 1em;
}
#search-se-menu [role="menuitemradio"]:focus {
background-color: var(--navbar-current-background-color-hover);
color: var(--navbar-level-1-color);
border-radius: 10px;
}
#search-se-menu ul li .fa-check {
display: none;
}
#search-se-menu ul li.selected .fa-check {
display: inline;
}

17
doc/_templates/gsearch.html vendored Normal file
View File

@ -0,0 +1,17 @@
{%- extends "!search.html" %}
{%- block scripts %}
{{ super.super() }}
<link rel="stylesheet" href="{{ pathto('_static/css/gcs.css', 1) }}" type="text/css" />
{%- endblock %}
{% block footer %}
{{ super.super() }}
{% endblock %}
{% block body %}
<h2>{{ _('Search Results') }}</h2>
<script async src="https://cse.google.com/cse.js?cx={{ google_searchengine_id }}">
</script>
<div class="gcse-searchresults-only"></div>
{% endblock %}

View File

@ -1,28 +0,0 @@
{%- extends "!search.html" %}
{%- block scripts %}
{% if google_searchengine_id %}
{{ super.super() }}
<link rel="stylesheet" href="{{ pathto('_static/css/gcs.css', 1) }}" type="text/css" />
{% else %}
{{ super() }}
{% endif %}
{%- endblock %}
{% block footer %}
{% if google_searchengine_id %}
{{ super.super() }}
{% else %}
{{ super() }}
{% endif %}
{% endblock %}
{% block body %}
{% if google_searchengine_id %}
<h2>{{ _('Search Results') }}</h2>
<script async src="https://cse.google.com/cse.js?cx={{ google_searchengine_id }}">
</script>
<div class="gcse-searchresults-only"></div>
{% else %}
{{ super() }}
{% endif %}
{% endblock %}

130
doc/_templates/searchbox.html vendored Normal file
View File

@ -0,0 +1,130 @@
{#
Override the default searchbox from RTD theme to provide the ability to select a search method
(ex. built-in search, Google Custom Search, ...)
#}
{%- if ('singlehtml' not in builder) %}
<div class="search-container" role="search">
<form id="rtd-search-form" class="wy-form" action="{{ pathto('search') }}" method="get">
<input type="search" name="q" placeholder="{{ _('Search docs') }}"
aria-label="{{ _('Search docs') }}" />
{%- if google_searchengine_id is defined %}
<span id="search-se-settings-icon" class="fa fa-gear" role="button" tabindex="0"
title="Search settings" aria-label="Search settings"
aria-haspopup="true" aria-controls="search-se-menu" aria-expanded="false"
onclick="toggleSearchEngineSettingsMenu()">
</span>
<div id="search-se-menu" role="menu" aria-labelledby="search-se-settings-icon">
<ul>
<li id="search-se-menuitem-sphinx" role="menuitemradio" tabindex="-1"
aria-label="Built-in search" onclick="switchSearchEngine('sphinx')">
Built-in search <span class="fa fa-check">
</li>
<li id="search-se-menuitem-google" role="menuitemradio" tabindex="-1"
aria-label="Google search" onclick="switchSearchEngine('google')">
Google search <span class="fa fa-check">
</li>
</div>
{%- endif %}
<input type="hidden" name="check_keywords" value="yes" />
<input type="hidden" name="area" value="default" />
</form>
</div>
{%- if google_searchengine_id is defined %}
<script>
(function () {
var form = document.getElementById("rtd-search-form");
var searchMenu = document.getElementById("search-se-menu");
var isBrowsingLatest = window.location.pathname.startsWith("/latest");
var preferenceKey = "search-se-" + (isBrowsingLatest ? "latest" : "default");
var query = new URLSearchParams(window.location.search).get("q");
if (query !== null) {
form.q.value = query;
form.q.focus();
}
// Load the saved search preference. Defaults to Google when browsing "/latest" documentation,
// built-in Sphinx search otherwise.
var engine = localStorage.getItem(preferenceKey);
if (engine === null) {
engine = isBrowsingLatest ? "google" : "sphinx";
}
setActiveSearchEngine(engine);
setSearchEngineSettingsMenuVisibility = function (visible) {
searchMenu.style.display = visible ? "block" : "none";
document
.getElementById("search-se-settings-icon")
.setAttribute("aria-expanded", visible ? "true" : "false");
};
window.toggleSearchEngineSettingsMenu = function () {
isVisible = searchMenu.style.display === "block";
setSearchEngineSettingsMenuVisibility(!isVisible);
};
function setActiveSearchEngine(engine) {
if(engine === "sphinx") {
form.action = "{{ pathto('search') }}";
form.q.placeholder = "Search docs (built-in search)";
} else {
form.action = "{{ pathto('gsearch') }}";
form.q.placeholder = "Search docs (powered by Google)";
}
var selectedElement = document.getElementById("search-se-menuitem-" + engine);
var otherElement = document.getElementById(
"search-se-menuitem-" + (engine === "sphinx" ? "google" : "sphinx")
);
selectedElement.classList.add("selected");
selectedElement.setAttribute("aria-checked", "true");
otherElement.classList.remove("selected");
otherElement.setAttribute("aria-checked", "false");
}
window.switchSearchEngine = function (engine) {
setActiveSearchEngine(engine);
localStorage.setItem(preferenceKey, engine);
setSearchEngineSettingsMenuVisibility(false);
form.q.focus();
if (form.q.value !== "") {
form.submit();
}
};
// Close the dropdown if the user clicks outside of it
window.onclick = function (event) {
if (!event.target.matches("#search-se-settings-icon")) {
if (searchMenu.style.display === "block") {
setSearchEngineSettingsMenuVisibility(false);
}
}
};
document.addEventListener("keydown", function (event) {
if(searchMenu.style.display === "none") return;
let menuItems = document.querySelectorAll('[role="menuitemradio"]');
let currentIndex = Array.from(menuItems).findIndex((item) => item === document.activeElement);
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
let nextIndex = event.key === "ArrowDown" ? currentIndex + 1 : currentIndex - 1;
if (nextIndex >= menuItems.length) nextIndex = 0;
if (nextIndex < 0) nextIndex = menuItems.length - 1;
menuItems[nextIndex].focus();
event.preventDefault();
} else if (event.key === "Enter") {
let activeItem = document.activeElement;
if (activeItem && activeItem.getAttribute("role") === "menuitemradio") {
activeItem.click();
event.preventDefault();
}
}
});
})();
</script>
{%- endif %}
{%- endif %}

View File

@ -153,6 +153,9 @@ html_split_index = True
html_show_sourcelink = False
html_show_sphinx = False
html_search_scorer = str(ZEPHYR_BASE / "doc" / "_static" / "js" / "scorer.js")
html_additional_pages = {
"gsearch": "gsearch.html"
}
is_release = tags.has("release") # pylint: disable=undefined-variable
reference_prefix = ""