Skip to content

DOC use Algolia for the search bar #29138

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 28 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
99e68d2
DOC use Algolia for the search bar
glemaitre May 30, 2024
e404189
[doc quick] trigger doc quick build
glemaitre May 30, 2024
e8ecf02
[doc quick] actually use extensioon
glemaitre May 30, 2024
d315f9e
[doc quick] fix name extension
glemaitre May 30, 2024
041ed9f
[doc quick] solve the issue with sphinx-galelry
glemaitre May 30, 2024
a95887e
iter
glemaitre May 30, 2024
5a5d37c
[doc quick] customize docsearch modal
glemaitre May 30, 2024
14e4644
Merge remote-tracking branch 'origin/main' into algolia_v2
glemaitre May 31, 2024
f4e0c47
udpate
glemaitre May 31, 2024
f4dc575
[doc quick] trigger doc build
glemaitre May 31, 2024
941dabd
[doc quick] change color
glemaitre May 31, 2024
f20781b
[doc quick] iter
glemaitre Jun 1, 2024
5104596
[doc quick] iter
glemaitre Jun 1, 2024
7279389
[doc quick] iter
glemaitre Jun 1, 2024
f50c371
[doc quick] iter
glemaitre Jun 1, 2024
1bf3478
[doc quick] iter
glemaitre Jun 1, 2024
8e0b5df
[doc quick] change color
glemaitre Jun 1, 2024
69cb0e8
[doc quick] apply change from Charlie
glemaitre Jun 2, 2024
c65b4c2
[doc quick] typo
glemaitre Jun 2, 2024
cb79d34
Update doc/scss/custom.scss
glemaitre Jun 2, 2024
cebb781
Merge remote-tracking branch 'upstream/main' into glemaitre--algolia_v2
Charlie-XIAO Jul 27, 2024
c92e6c4
lock files
Charlie-XIAO Jul 27, 2024
b556b32
Merge remote-tracking branch 'upstream/main' into glemaitre--algolia_v2
Charlie-XIAO Aug 13, 2024
eee619e
improve search and create search results page
Charlie-XIAO Aug 13, 2024
5c489f0
retrigger workflows
Charlie-XIAO Aug 13, 2024
6faa0f4
retrigger workflows
Charlie-XIAO Aug 13, 2024
424c34e
Merge remote-tracking branch 'upstream/main' into glemaitre--algolia_v2
Charlie-XIAO Aug 13, 2024
d62827d
Merge branch 'main' into algolia_v2
Charlie-XIAO Aug 13, 2024
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
4 changes: 4 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ jobs:
# dependencies as long as we can avoid raising warnings with more recent
# versions of the same dependencies.
- SKLEARN_WARNINGS_AS_ERRORS: '0'
# Test not using Algolia search
- SKLEARN_DOC_USE_ALGOLIA_SEARCH: '0'
steps:
- checkout
- run: ./build_tools/circle/checkout_merge_commit.sh
Expand Down Expand Up @@ -65,6 +67,8 @@ jobs:
# Make sure that we fail if the documentation build generates warnings with
# recent versions of the dependencies.
- SKLEARN_WARNINGS_AS_ERRORS: '1'
# Build the actual documentation with Algolia search enabled
- SKLEARN_DOC_USE_ALGOLIA_SEARCH: '1'
steps:
- checkout
- run: ./build_tools/circle/checkout_merge_commit.sh
Expand Down
42 changes: 42 additions & 0 deletions doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@
# that need plotly.
pass

# Set the environment variable to use Algolia docsearch to overwrite the default sphinx
# local search; this is used in CI
use_algolia = os.environ.get("SKLEARN_DOC_USE_ALGOLIA_SEARCH", "0") != "0"

# -- General configuration ---------------------------------------------------

# Add any Sphinx extension module names here, as strings. They can be
Expand Down Expand Up @@ -286,6 +290,14 @@
"announcement": None,
}

if use_algolia:
# Remove the sphinx searchbox from the persistent field and add Algolia searchbox
# to the start field; note that Algolia searchbox can only be placed in the start
# field because all other fields have multiple slots to adapt to different screen
# sizes while Algolia can hydrate only one slot
html_theme_options["navbar_start"].append("algolia-searchbox")
html_theme_options["navbar_persistent"] = []

# Add any paths that contain custom themes here, relative to this directory.
# html_theme_path = ["themes"]

Expand Down Expand Up @@ -330,6 +342,9 @@
# template names.
html_additional_pages = {"index": "index.html"}

if use_algolia:
html_additional_pages["algolia-search"] = "algolia-search.html"

# Additional files to copy
# html_extra_path = []

Expand Down Expand Up @@ -372,6 +387,28 @@ def add_js_css_files(app, pagename, templatename, context, doctree):
elif pagename.startswith("modules/generated/"):
app.add_css_file("styles/api.css")

if use_algolia:
# If using Algolia search, load Algolia credentials and index name so that they
# are accessible in JavaScript
app.add_js_file(
None,
body=(
'SKLEARN_ALGOLIA_APP_ID = "WAC7N12TSK";\n'
'SKLEARN_ALGOLIA_API_KEY = "85fcdebd88be36ce665548bbbf328519";\n'
'SKLEARN_ALGOLIA_INDEX_NAME = "scikit-learn";'
),
)
if pagename != "algolia-search":
# For all pages except for search page, load Algolia docsearch CSS and JS to
# enable the search field in the navbar
app.add_js_file(
"https://cdn.jsdelivr.net/npm/@docsearch/js@3.6.1",
loading_method="defer",
)
app.add_js_file("scripts/algolia-searchbox.js", loading_method="defer")
app.add_css_file("https://cdn.jsdelivr.net/npm/@docsearch/css@3.6.1")
app.add_css_file("styles/algolia-searchbox.css")


# If false, no module index is generated.
html_domain_indices = False
Expand Down Expand Up @@ -1017,3 +1054,8 @@ def infer_next_release_versions():
# Render the template and write to the target
with (Path(".") / f"{rst_target_name}.rst").open("w", encoding="utf-8") as f:
f.write(t.render(**kwargs))

# Algolia docsearch setting
docsearch_app_id = os.getenv("DOCSEARCH_APP_ID")
docsearch_api_key = os.getenv("DOCSEARCH_API_KEY")
docsearch_index_name = "scikit-learn"
146 changes: 146 additions & 0 deletions doc/js/scripts/algolia-search.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/**
* This script is used initialize Algolia DocSearch on the Algolia search page. It will
* hydrate the search page (see `doc/templates/search.html`) and activate the search
* functionalities.
*/

document.addEventListener("DOMContentLoaded", () => {
let timer;
const timeout = 500; // Debounce search-as-you-type

const searchClient = algoliasearch(
SKLEARN_ALGOLIA_APP_ID,
SKLEARN_ALGOLIA_API_KEY
);

const search = instantsearch({
indexName: SKLEARN_ALGOLIA_INDEX_NAME,
initialUiState: {
[SKLEARN_ALGOLIA_INDEX_NAME]: {
query: new URLSearchParams(window.location.search).get("q") || "",
},
},
searchClient,
});

search.addWidgets([
// The powered-by widget as the heading
instantsearch.widgets.poweredBy({
container: "#docsearch-powered-by-light",
theme: "light",
}),
instantsearch.widgets.poweredBy({
container: "#docsearch-powered-by-dark",
theme: "dark",
}),
// The search input box
instantsearch.widgets.searchBox({
container: "#docsearch-container",
placeholder: "Search the docs ...",
autofocus: true,
// Debounce the search input to avoid making too many requests
queryHook(query, refine) {
clearTimeout(timer);
timer = setTimeout(() => refine(query), timeout);
},
}),
// The search statistics before the list of results
instantsearch.widgets.stats({
container: "#docsearch-stats",
templates: {
text: (data, { html }) => {
if (data.query === "") {
return "";
}

let count;
if (data.hasManyResults) {
count = `${data.nbHits} results`;
} else if (data.hasOneResult) {
count = "1 result";
} else {
count = "no results";
}
return html`
<div class="sk-search-stats-heading">Search Results</div>
<p class="sk-search-stats">
Search finished, found ${count} matching the search query in
${data.processingTimeMS}ms.
</p>
`;
},
},
}),
// The list of search results
instantsearch.widgets.infiniteHits({
container: "#docsearch-hits",
transformItems: (items, { results }) => {
if (results.query === "") {
return [];
}
return items;
},
templates: {
item: (hit, { html, components }) => {
const hierarchy = Object.entries(hit._highlightResult.hierarchy);
const lastKey = hierarchy[hierarchy.length - 1][0];

const sharedHTML = html`
<a class="sk-search-item-header" href="${hit.url}">
${components.Highlight({
hit,
attribute: `hierarchy.${lastKey}`,
})}
</a>
<div class="sk-search-item-path">
${components.Highlight({ hit, attribute: "hierarchy.lvl0" })}
${hierarchy.slice(1, -1).map(([key, _]) => {
return html`
<span class="sk-search-item-path-divider">»</span>
${components.Highlight({
hit,
attribute: `hierarchy.${key}`,
})}
`;
})}
</div>
`;

if (hit.type === "content") {
return html`
${sharedHTML}
<p class="sk-search-item-context">
${components.Highlight({ hit, attribute: "content" })}
</p>
`;
} else {
return sharedHTML;
}
},
// We have stats widget that can imply "no results"
empty: () => {
return "";
},
},
}),
// Additional configuration of the widgets
instantsearch.widgets.configure({
hitsPerPage: 200,
}),
]);

search.start();

// Apart from the loading indicator in the search form, also show loading information
// at the bottom so when clicking on "load more" we also have some feedback
search.on("render", () => {
const container = document.getElementById("docsearch-loading-indicator");
if (search.status === "stalled") {
container.innerText = "Loading search results...";
container.style.marginTop = "0.4rem";
} else {
container.innerText = "";
container.style.marginTop = "0";
}
});
});
54 changes: 54 additions & 0 deletions doc/js/scripts/algolia-searchbox.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* This script is used initialize Algolia DocSearch on each page. It will hydrate the
* container with ID `docsearch` (see `doc/templates/algolia-searchbox.html`) with the
* Algolia search widget.
*/

document.addEventListener("DOMContentLoaded", () => {
// Figure out how to route to the search page from the current page where we will show
// all search results
const pagename = DOCUMENTATION_OPTIONS.pagename;
let searchPageHref = "./";
for (let i = 0; i < pagename.split("/").length - 1; i++) {
searchPageHref += "../";
}
searchPageHref += "algolia-search.html";

// Initialize the Algolia DocSearch widget
docsearch({
container: "#docsearch",
appId: SKLEARN_ALGOLIA_APP_ID,
apiKey: SKLEARN_ALGOLIA_API_KEY,
indexName: SKLEARN_ALGOLIA_INDEX_NAME,
placeholder: "Search the docs ... (Alt+Enter to go to search page)",
// Redirect to the search page with the corresponding query
resultsFooterComponent: ({ state }) => ({
type: "a",
ref: undefined,
constructor: undefined,
key: state.query,
props: {
id: "sk-search-all-results-link",
href: `${searchPageHref}?q=${state.query}`,
children: `Check all results...`,
},
__v: null,
}),
});

// Ctrl-Alt to navigate to the all results page
document.addEventListener("keydown", (e) => {
if (e.altKey && e.key === "Enter") {
e.preventDefault();

// Click the link if it exists so the query is preserved; otherwise navigate to
// the search page without query
const link = document.getElementById("sk-search-all-results-link");
if (link) {
link.click();
} else {
window.location.href = searchPageHref;
}
}
});
});
Loading