From 8fb380d465fcc1dd51ff41bdf51db4b604e75234 Mon Sep 17 00:00:00 2001 From: Johannes Marbach Date: Tue, 24 Mar 2026 15:11:52 +0100 Subject: [PATCH] Load and render main results one by one Signed-off-by: Johannes Marbach --- assets/js/offline-search.js | 215 ++++++++++++++++++------------------ 1 file changed, 110 insertions(+), 105 deletions(-) diff --git a/assets/js/offline-search.js b/assets/js/offline-search.js index 55249083..cbdac0ae 100644 --- a/assets/js/offline-search.js +++ b/assets/js/offline-search.js @@ -73,7 +73,7 @@ search backend. } // - // Kick off the search and collect the results. + // Check if we need to do a search at all. // const searchQuery = $targetSearchInput.val(); @@ -81,37 +81,8 @@ search backend. return; } - // Show the results popover with a spinner while we're busy. - const $spinner = $("
") - .addClass("spinner-container") - .append($("
") - .addClass("spinner-border") - .attr("role", "status") - .append($("
") - .addClass("visually-hidden") - .text("Loading..."))) - .append($("

") - .text("Loading...")); - const popover = new bootstrap.Popover($targetSearchInput[0], { - content: $spinner[0], - html: true, - customClass: "td-offline-search-results", - placement: "bottom", - }); - popover.show(); - - // Kick off the search. - const search = await pagefind.debouncedSearch(searchQuery); - if (search === null) { - // A more recent search call has been made, nothing to do. - return; - } - - // Load all result details. We may want to switch to a paged UI in future for better performance. - const results = await Promise.all(search.results.slice(0, 100).map(r => r.data())); - // - // Construct the search results html. + // Prepare the results popover. // const $html = $("

"); @@ -149,87 +120,56 @@ search backend. }); $html.append($searchResultBody); - if (results.length === 0) { + // Append a spinner while we're busy. + const $spinner = $("
") + .addClass("spinner-container") + .append($("
") + .addClass("spinner-border") + .attr("role", "status") + .append($("
") + .addClass("visually-hidden") + .text("Loading..."))) + .append($("

") + .text("Loading...")); + $searchResultBody.append($spinner) + + // Display the popover. + const popover = new bootstrap.Popover($targetSearchInput[0], { + content: $html[0], + html: true, + customClass: "td-offline-search-results", + placement: "bottom", + }); + popover.show(); + + // + // Kick off the search, load the results and inject them into the popover. + // + + const search = await pagefind.debouncedSearch(searchQuery); + if (search === null) { + // A more recent search call has been made, nothing to do. + return; + } + + if (search.results.length === 0) { $searchResultBody.append( $("

").text(`No results found for query "${searchQuery}"`) ); } else { - results.forEach((r, index_r) => { - // Add the main result's page title. - $searchResultBody.append($("

") - .append($("") - .css({ - fontSize: "1.2rem", - }) - .attr("href", r.url) - .text(r.meta.title)) - .append($("") - .addClass("text-body-secondary") - .text(` – ${r.sub_results.length} ${resultsString(r.sub_results.length)}`))); - - // Render the first 3 subresults per page and wrap the rest - // in a collapsed container. - const LIMIT = 3; - let $wrapper = null; + for (const [index, result] of search.results.entries()) { + // Insert a container for the result *before* the spinner. This + // will push down the spinner as new content is loaded and keep + // it at the end of the popover. + const $container = $("
"); + $spinner.before($container); - r.sub_results.forEach((s, index_s) => { - if (index_s === LIMIT) { - const num_hidden_results = r.sub_results.length - index_s; - const wrapper_id = `collapsible-subresults-${index_r}`; - const $action = $("").text("▶ Show"); - const $expander = $("