Skip to content

Commit 52b1f17

Browse files
authored
Fix the race condition causing search not to initialize properly (#2593)
1 parent ed4f77e commit 52b1f17

File tree

2 files changed

+166
-142
lines changed

2 files changed

+166
-142
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
55

66
## Unreleased
77

8+
### Fixed
9+
10+
* The search modal no longer runs into a race condition with loading the search index and consistently opens correctly. ([#2593])
11+
812
### Other
13+
914
* Documenter now uses [Runic.jl](https://github.com/fredrikekre/Runic.jl) for code formatting.
1015

1116
## Version [v1.7.0] - 2024-09-04
@@ -1904,6 +1909,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
19041909
[#2562]: https://github.com/JuliaDocs/Documenter.jl/issues/2562
19051910
[#2569]: https://github.com/JuliaDocs/Documenter.jl/issues/2569
19061911
[#2571]: https://github.com/JuliaDocs/Documenter.jl/issues/2571
1912+
[#2593]: https://github.com/JuliaDocs/Documenter.jl/issues/2593
19071913
[JuliaLang/julia#36953]: https://github.com/JuliaLang/julia/issues/36953
19081914
[JuliaLang/julia#38054]: https://github.com/JuliaLang/julia/issues/38054
19091915
[JuliaLang/julia#39841]: https://github.com/JuliaLang/julia/issues/39841

assets/html/js/search.js

Lines changed: 160 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -367,172 +367,190 @@ function worker_function(documenterSearchIndex, documenterBaseURL, filters) {
367367
};
368368
}
369369

370-
// `worker = Threads.@spawn worker_function(documenterSearchIndex)`, but in JavaScript!
371-
const filters = [
372-
...new Set(documenterSearchIndex["docs"].map((x) => x.category)),
373-
];
374-
const worker_str =
375-
"(" +
376-
worker_function.toString() +
377-
")(" +
378-
JSON.stringify(documenterSearchIndex["docs"]) +
379-
"," +
380-
JSON.stringify(documenterBaseURL) +
381-
"," +
382-
JSON.stringify(filters) +
383-
")";
384-
const worker_blob = new Blob([worker_str], { type: "text/javascript" });
385-
const worker = new Worker(URL.createObjectURL(worker_blob));
386-
387370
/////// SEARCH MAIN ///////
388371

389-
// Whether the worker is currently handling a search. This is a boolean
390-
// as the worker only ever handles 1 or 0 searches at a time.
391-
var worker_is_running = false;
392-
393-
// The last search text that was sent to the worker. This is used to determine
394-
// if the worker should be launched again when it reports back results.
395-
var last_search_text = "";
396-
397-
// The results of the last search. This, in combination with the state of the filters
398-
// in the DOM, is used compute the results to display on calls to update_search.
399-
var unfiltered_results = [];
400-
401-
// Which filter is currently selected
402-
var selected_filter = "";
403-
404-
$(document).on("input", ".documenter-search-input", function (event) {
405-
if (!worker_is_running) {
406-
launch_search();
407-
}
408-
});
409-
410-
function launch_search() {
411-
worker_is_running = true;
412-
last_search_text = $(".documenter-search-input").val();
413-
worker.postMessage(last_search_text);
414-
}
415-
416-
worker.onmessage = function (e) {
417-
if (last_search_text !== $(".documenter-search-input").val()) {
418-
launch_search();
419-
} else {
420-
worker_is_running = false;
421-
}
422-
423-
unfiltered_results = e.data;
424-
update_search();
425-
};
372+
function runSearchMainCode() {
373+
// `worker = Threads.@spawn worker_function(documenterSearchIndex)`, but in JavaScript!
374+
const filters = [
375+
...new Set(documenterSearchIndex["docs"].map((x) => x.category)),
376+
];
377+
const worker_str =
378+
"(" +
379+
worker_function.toString() +
380+
")(" +
381+
JSON.stringify(documenterSearchIndex["docs"]) +
382+
"," +
383+
JSON.stringify(documenterBaseURL) +
384+
"," +
385+
JSON.stringify(filters) +
386+
")";
387+
const worker_blob = new Blob([worker_str], { type: "text/javascript" });
388+
const worker = new Worker(URL.createObjectURL(worker_blob));
389+
390+
// Whether the worker is currently handling a search. This is a boolean
391+
// as the worker only ever handles 1 or 0 searches at a time.
392+
var worker_is_running = false;
393+
394+
// The last search text that was sent to the worker. This is used to determine
395+
// if the worker should be launched again when it reports back results.
396+
var last_search_text = "";
397+
398+
// The results of the last search. This, in combination with the state of the filters
399+
// in the DOM, is used compute the results to display on calls to update_search.
400+
var unfiltered_results = [];
401+
402+
// Which filter is currently selected
403+
var selected_filter = "";
404+
405+
$(document).on("input", ".documenter-search-input", function (event) {
406+
if (!worker_is_running) {
407+
launch_search();
408+
}
409+
});
426410

427-
$(document).on("click", ".search-filter", function () {
428-
if ($(this).hasClass("search-filter-selected")) {
429-
selected_filter = "";
430-
} else {
431-
selected_filter = $(this).text().toLowerCase();
411+
function launch_search() {
412+
worker_is_running = true;
413+
last_search_text = $(".documenter-search-input").val();
414+
worker.postMessage(last_search_text);
432415
}
433416

434-
// This updates search results and toggles classes for UI:
435-
update_search();
436-
});
417+
worker.onmessage = function (e) {
418+
if (last_search_text !== $(".documenter-search-input").val()) {
419+
launch_search();
420+
} else {
421+
worker_is_running = false;
422+
}
437423

438-
/**
439-
* Make/Update the search component
440-
*/
441-
function update_search() {
442-
let querystring = $(".documenter-search-input").val();
424+
unfiltered_results = e.data;
425+
update_search();
426+
};
443427

444-
if (querystring.trim()) {
445-
if (selected_filter == "") {
446-
results = unfiltered_results;
428+
$(document).on("click", ".search-filter", function () {
429+
if ($(this).hasClass("search-filter-selected")) {
430+
selected_filter = "";
447431
} else {
448-
results = unfiltered_results.filter((result) => {
449-
return selected_filter == result.category.toLowerCase();
450-
});
432+
selected_filter = $(this).text().toLowerCase();
451433
}
452434

453-
let search_result_container = ``;
454-
let modal_filters = make_modal_body_filters();
455-
let search_divider = `<div class="search-divider w-100"></div>`;
435+
// This updates search results and toggles classes for UI:
436+
update_search();
437+
});
456438

457-
if (results.length) {
458-
let links = [];
459-
let count = 0;
460-
let search_results = "";
461-
462-
for (var i = 0, n = results.length; i < n && count < 200; ++i) {
463-
let result = results[i];
464-
if (result.location && !links.includes(result.location)) {
465-
search_results += result.div;
466-
count++;
467-
links.push(result.location);
468-
}
469-
}
439+
/**
440+
* Make/Update the search component
441+
*/
442+
function update_search() {
443+
let querystring = $(".documenter-search-input").val();
470444

471-
if (count == 1) {
472-
count_str = "1 result";
473-
} else if (count == 200) {
474-
count_str = "200+ results";
445+
if (querystring.trim()) {
446+
if (selected_filter == "") {
447+
results = unfiltered_results;
475448
} else {
476-
count_str = count + " results";
449+
results = unfiltered_results.filter((result) => {
450+
return selected_filter == result.category.toLowerCase();
451+
});
477452
}
478-
let result_count = `<div class="is-size-6">${count_str}</div>`;
479453

480-
search_result_container = `
454+
let search_result_container = ``;
455+
let modal_filters = make_modal_body_filters();
456+
let search_divider = `<div class="search-divider w-100"></div>`;
457+
458+
if (results.length) {
459+
let links = [];
460+
let count = 0;
461+
let search_results = "";
462+
463+
for (var i = 0, n = results.length; i < n && count < 200; ++i) {
464+
let result = results[i];
465+
if (result.location && !links.includes(result.location)) {
466+
search_results += result.div;
467+
count++;
468+
links.push(result.location);
469+
}
470+
}
471+
472+
if (count == 1) {
473+
count_str = "1 result";
474+
} else if (count == 200) {
475+
count_str = "200+ results";
476+
} else {
477+
count_str = count + " results";
478+
}
479+
let result_count = `<div class="is-size-6">${count_str}</div>`;
480+
481+
search_result_container = `
482+
<div class="is-flex is-flex-direction-column gap-2 is-align-items-flex-start">
483+
${modal_filters}
484+
${search_divider}
485+
${result_count}
486+
<div class="is-clipped w-100 is-flex is-flex-direction-column gap-2 is-align-items-flex-start has-text-justified mt-1">
487+
${search_results}
488+
</div>
489+
</div>
490+
`;
491+
} else {
492+
search_result_container = `
481493
<div class="is-flex is-flex-direction-column gap-2 is-align-items-flex-start">
482494
${modal_filters}
483495
${search_divider}
484-
${result_count}
485-
<div class="is-clipped w-100 is-flex is-flex-direction-column gap-2 is-align-items-flex-start has-text-justified mt-1">
486-
${search_results}
487-
</div>
488-
</div>
496+
<div class="is-size-6">0 result(s)</div>
497+
</div>
498+
<div class="has-text-centered my-5 py-5">No result found!</div>
489499
`;
490-
} else {
491-
search_result_container = `
492-
<div class="is-flex is-flex-direction-column gap-2 is-align-items-flex-start">
493-
${modal_filters}
494-
${search_divider}
495-
<div class="is-size-6">0 result(s)</div>
496-
</div>
497-
<div class="has-text-centered my-5 py-5">No result found!</div>
498-
`;
499-
}
500+
}
500501

501-
if ($(".search-modal-card-body").hasClass("is-justify-content-center")) {
502-
$(".search-modal-card-body").removeClass("is-justify-content-center");
503-
}
502+
if ($(".search-modal-card-body").hasClass("is-justify-content-center")) {
503+
$(".search-modal-card-body").removeClass("is-justify-content-center");
504+
}
504505

505-
$(".search-modal-card-body").html(search_result_container);
506-
} else {
507-
if (!$(".search-modal-card-body").hasClass("is-justify-content-center")) {
508-
$(".search-modal-card-body").addClass("is-justify-content-center");
506+
$(".search-modal-card-body").html(search_result_container);
507+
} else {
508+
if (!$(".search-modal-card-body").hasClass("is-justify-content-center")) {
509+
$(".search-modal-card-body").addClass("is-justify-content-center");
510+
}
511+
512+
$(".search-modal-card-body").html(`
513+
<div class="has-text-centered my-5 py-5">Type something to get started!</div>
514+
`);
509515
}
516+
}
510517

511-
$(".search-modal-card-body").html(`
512-
<div class="has-text-centered my-5 py-5">Type something to get started!</div>
513-
`);
518+
/**
519+
* Make the modal filter html
520+
*
521+
* @returns string
522+
*/
523+
function make_modal_body_filters() {
524+
let str = filters
525+
.map((val) => {
526+
if (selected_filter == val.toLowerCase()) {
527+
return `<a href="javascript:;" class="search-filter search-filter-selected"><span>${val}</span></a>`;
528+
} else {
529+
return `<a href="javascript:;" class="search-filter"><span>${val}</span></a>`;
530+
}
531+
})
532+
.join("");
533+
534+
return `
535+
<div class="is-flex gap-2 is-flex-wrap-wrap is-justify-content-flex-start is-align-items-center search-filters">
536+
<span class="is-size-6">Filters:</span>
537+
${str}
538+
</div>`;
514539
}
515540
}
516541

517-
/**
518-
* Make the modal filter html
519-
*
520-
* @returns string
521-
*/
522-
function make_modal_body_filters() {
523-
let str = filters
524-
.map((val) => {
525-
if (selected_filter == val.toLowerCase()) {
526-
return `<a href="javascript:;" class="search-filter search-filter-selected"><span>${val}</span></a>`;
527-
} else {
528-
return `<a href="javascript:;" class="search-filter"><span>${val}</span></a>`;
529-
}
530-
})
531-
.join("");
532-
533-
return `
534-
<div class="is-flex gap-2 is-flex-wrap-wrap is-justify-content-flex-start is-align-items-center search-filters">
535-
<span class="is-size-6">Filters:</span>
536-
${str}
537-
</div>`;
542+
function waitUntilSearchIndexAvailable() {
543+
// It is possible that the documenter.js script runs before the page
544+
// has finished loading and documenterSearchIndex gets defined.
545+
// So we need to wait until the search index actually loads before setting
546+
// up all the search-related stuff.
547+
if (typeof documenterSearchIndex !== "undefined") {
548+
runSearchMainCode();
549+
} else {
550+
console.warn("Search Index not available, waiting");
551+
setTimeout(waitUntilSearchIndexAvailable, 1000);
552+
}
538553
}
554+
555+
// The actual entry point to the search code
556+
waitUntilSearchIndexAvailable();

0 commit comments

Comments
 (0)