Skip to content

Commit 73ba3a5

Browse files
fix: search bar (#23459)
Fix for intermittent bug that happens on the docs site when using the search bar/search drop-down, reported by Eng - Fixed disconnected dropdown to keep dropdown attached to search bar - Added viewport boundary detection for window resizing - Made search icon clickable (critical for mobile) Tested on: - Chrome - Safari - Mobile https://docker.atlassian.net/browse/ENGDOCS-3002
1 parent 2861f28 commit 73ba3a5

File tree

2 files changed

+347
-273
lines changed

2 files changed

+347
-273
lines changed

layouts/_default/search.html

Lines changed: 170 additions & 164 deletions
Original file line numberDiff line numberDiff line change
@@ -1,185 +1,191 @@
1-
{{ define "left" }}
2-
{{ partial "sidebar/mainnav.html" . }}
3-
{{ end }}
4-
5-
{{ define "main" }}
6-
<article class="prose dark:prose-invert max-w-none">
7-
<h1 class="py-4">{{ .Title }}</h1>
8-
{{ .Content }}
9-
<div class="not-prose">
10-
<div class="flex justify-between gap-8 flex-row">
11-
<input type="search" id="search-page-input"
12-
class="ring-3-gray-light-200 dark:ring-3-gray-dark-400 dark:bg-background-dark focus:ring-3-blue-light dark:focus:ring-3-blue-dark ring-3-[1.5px] w-full max-w-xl min-w-0 rounded-sm bg-white px-4 py-2 outline-hidden"
13-
placeholder="Search…" tabindex="0" />
14-
<div class="admonition flex flex-row items-center gap-1">
15-
<p>Not finding what you're looking for? Try</p>
16-
<button onclick="askAI('search-page-input')" class="topbar-button bg-blue-400/95 border-blue-300 hover:bg-blue-400/90">
17-
<span>Ask&nbsp;AI</span>
18-
<span class="icon-svg">
19-
{{ partial "utils/svg.html" "/icons/sparkle.svg" }}
20-
</span>
21-
</button>
22-
</div>
23-
</div>
24-
<hr class="border-divider-light dark:border-divider-dark" />
25-
<div id="search-page-results">
26-
<!-- results -->
1+
{{ define "left" }}{{ partial "sidebar/mainnav.html" . }}{{ end }} {{ define
2+
"main" }}
3+
<article class="prose dark:prose-invert max-w-none">
4+
<h1 class="py-4">{{ .Title }}</h1>
5+
{{ .Content }}
6+
<div class="not-prose">
7+
<div class="flex flex-col items-center justify-between gap-8 lg:flex-row">
8+
<input
9+
type="search"
10+
id="search-page-input"
11+
class="ring-3-gray-light-200 dark:ring-3-gray-dark-400 dark:bg-background-dark focus:ring-3-blue-light dark:focus:ring-3-blue-dark ring-3-[1.5px] outline-hidden w-full min-w-0 rounded-sm bg-white px-4 py-2 lg:max-w-xl"
12+
placeholder="Search…"
13+
tabindex="0"
14+
/>
15+
<div class="flex items-center gap-2">
16+
<p class="text-sm text-gray-600 dark:text-gray-400">
17+
Not finding what you're looking for? Try
18+
</p>
19+
<button
20+
onclick="askAI('search-page-input')"
21+
class="topbar-button open-kapa-widget"
22+
>
23+
<span>Ask&nbsp;AI</span>
24+
<span class="icon-svg">
25+
{{ partial "utils/svg.html" "/icons/sparkle.svg" }}
26+
</span>
27+
</button>
2728
</div>
2829
</div>
29-
</article>
30-
<script type="module">
31-
// Global variable to hold the pagefind module
32-
let pagefind;
33-
34-
// Initialize the pagefind module and fire a search if the query parameter exists
35-
window.addEventListener("load", async function () {
36-
// Hydrate pagefind
37-
pagefind = await import("/pagefind/pagefind.js");
38-
await pagefind.options({
39-
ranking: {
40-
termFrequency: 0.2,
41-
pageLength: 0.75,
42-
termSaturation: 1.4,
43-
termSimilarity: 6.0,
44-
},
45-
});
46-
47-
// Get the query parameter from the URL
48-
const urlParams = new URLSearchParams(window.location.search);
49-
const query = urlParams.get("q");
50-
51-
// If no query parameter is set, return
52-
if (!query) {
53-
return;
54-
}
55-
56-
const searchInput = document.getElementById("search-page-input");
57-
58-
// Set the value of the input field to the query parameter
59-
searchInput.value = query;
60-
61-
// Trigger the input event to simulate user typing
62-
const event = new Event("input", {
63-
bubbles: true,
64-
cancelable: true,
65-
});
66-
// Trigger the input event for the search input
67-
searchInput.dispatchEvent(event);
68-
searchInput.focus();
30+
<hr class="border-divider-light dark:border-divider-dark" />
31+
<div id="search-page-results">
32+
<!-- results -->
33+
</div>
34+
</div>
35+
</article>
36+
<script type="module">
37+
// Global variable to hold the pagefind module
38+
let pagefind;
39+
40+
// Initialize the pagefind module and fire a search if the query parameter exists
41+
window.addEventListener("load", async function () {
42+
// Hydrate pagefind
43+
pagefind = await import("/pagefind/pagefind.js");
44+
await pagefind.options({
45+
ranking: {
46+
termFrequency: 0.2,
47+
pageLength: 0.75,
48+
termSaturation: 1.4,
49+
termSimilarity: 6.0,
50+
},
6951
});
7052

71-
const searchPageInput = document.querySelector("#search-page-input");
72-
const searchPageResults = document.querySelector("#search-page-results");
53+
// Get the query parameter from the URL
54+
const urlParams = new URLSearchParams(window.location.search);
55+
const query = urlParams.get("q");
7356

74-
// onPageSearch returns 10 results per query
75-
async function onPageSearch(e) {
76-
pagefind.init();
77-
const query = e.target.value;
78-
79-
// Set the query parameter in the URL
80-
const params = new URLSearchParams(document.location.search);
81-
params.set("q", query);
57+
// If no query parameter is set, return
58+
if (!query) {
59+
return;
60+
}
8261

83-
// Default the current page to 1
84-
let currentPage = 1;
62+
const searchInput = document.getElementById("search-page-input");
8563

86-
// Check if the page parameter exists
87-
const page = params.get("page");
88-
// Calculate the range start based on the page parameter
89-
if (page) {
90-
currentPage = parseInt(page);
91-
}
92-
const rangeStart = (currentPage - 1) * 10;
93-
const rangeEnd = rangeStart + 10;
64+
// Set the value of the input field to the query parameter
65+
searchInput.value = query;
9466

95-
// Execute the search
96-
const search = await pagefind.debouncedSearch(query);
97-
// If no search results are found, exit
98-
if (search === null) {
67+
// Trigger the input event to simulate user typing
68+
const event = new Event("input", {
69+
bubbles: true,
70+
cancelable: true,
71+
});
72+
// Trigger the input event for the search input
73+
searchInput.dispatchEvent(event);
74+
searchInput.focus();
75+
});
76+
77+
const searchPageInput = document.querySelector("#search-page-input");
78+
const searchPageResults = document.querySelector("#search-page-results");
79+
80+
// onPageSearch returns 10 results per query
81+
async function onPageSearch(e) {
82+
pagefind.init();
83+
const query = e.target.value;
84+
85+
// Set the query parameter in the URL
86+
const params = new URLSearchParams(document.location.search);
87+
params.set("q", query);
88+
89+
// Default the current page to 1
90+
let currentPage = 1;
91+
92+
// Check if the page parameter exists
93+
const page = params.get("page");
94+
// Calculate the range start based on the page parameter
95+
if (page) {
96+
currentPage = parseInt(page);
97+
}
98+
const rangeStart = (currentPage - 1) * 10;
99+
const rangeEnd = rangeStart + 10;
100+
101+
// Execute the search
102+
const search = await pagefind.debouncedSearch(query);
103+
// If no search results are found, exit
104+
if (search === null) {
105+
return;
106+
} else {
107+
// total number of results
108+
const resultsLength = search.results.length;
109+
// Get the data for the search results
110+
// Slice the results based on the range start + 10
111+
const resultsData = await Promise.all(
112+
search.results.slice(rangeStart, rangeEnd).map((r) => r.data()),
113+
);
114+
// If the range does not have any results, display a message
115+
if (resultsData.length === 0) {
116+
searchPageResults.innerHTML = `<div class="p-4">No results found</div>`;
99117
return;
118+
}
119+
// Add an index to the results, for heap tracking
120+
const results = resultsData.map((item, index) => ({
121+
...item,
122+
index: index + 1,
123+
}));
124+
125+
// If the query is not empty, display the search results container
126+
if (query) {
127+
searchPageResults.classList.remove("hidden");
100128
} else {
101-
// total number of results
102-
const resultsLength = search.results.length;
103-
// Get the data for the search results
104-
// Slice the results based on the range start + 10
105-
const resultsData = await Promise.all(
106-
search.results.slice(rangeStart, rangeEnd).map((r) => r.data()),
107-
);
108-
// If the range does not have any results, display a message
109-
if (resultsData.length === 0) {
110-
searchPageResults.innerHTML = `<div class="p-4">No results found</div>`;
111-
return;
112-
}
113-
// Add an index to the results, for heap tracking
114-
const results = resultsData.map((item, index) => ({
115-
...item,
116-
index: index + 1,
117-
}));
118-
119-
// If the query is not empty, display the search results container
120-
if (query) {
121-
searchPageResults.classList.remove("hidden");
122-
} else {
123-
searchPageResults.classList.add("hidden");
124-
}
129+
searchPageResults.classList.add("hidden");
130+
}
125131

126-
// Generate the search results HTML
127-
let resultsHTML = `<div class="text-gray-400 dark:text-gray-500 p-2">${resultsLength} results</div>`;
128-
129-
// Map results to HTML
130-
resultsHTML += results
131-
.map((item) => {
132-
return `<div class="p-4">
133-
<div class="flex flex-col">
134-
<span class="text-gray-400 dark:texty-gray-dark text-sm">${item.meta.breadcrumbs}</span>
135-
<a class="link" href="${item.url}" data-query="${query}" data-index="${item.index}">${item.meta.title}</a>
136-
<p class="text-black dark:text-white overflow-hidden">…${item.excerpt}…</p>
137-
</div>
138-
</div>`;
139-
})
140-
.join("");
141-
// If the results length is greater than 10, display links to show more results
142-
if (resultsLength > 10) {
143-
resultsHTML += `<hr class="border-divider-light dark:border-divider-dark">`;
144-
resultsHTML += `<ul class="flex flex-wrap gap-1 pt-4 pb-8 justify-center text-sm">`;
145-
for (let i = 1; i <= resultsLength / 10; i++) {
146-
if (i == currentPage) {
147-
resultsHTML += `<li class="flex items-center justify-center">
132+
// Generate the search results HTML
133+
let resultsHTML = `<div class="text-gray-400 dark:text-gray-500 p-2">${resultsLength} results</div>`;
134+
135+
// Map results to HTML
136+
resultsHTML += results
137+
.map((item) => {
138+
return `<div class="p-4">
139+
<div class="flex flex-col">
140+
<span class="text-gray-400 dark:texty-gray-dark text-sm">${item.meta.breadcrumbs}</span>
141+
<a class="link" style="word-break: break-word; overflow-wrap: anywhere;" href="${item.url}" data-query="${query}" data-index="${item.index}">${item.meta.title}</a>
142+
<p class="text-black dark:text-white overflow-hidden" style="word-break: break-word; overflow-wrap: anywhere;">…${item.excerpt}…</p>
143+
</div>
144+
</div>`;
145+
})
146+
.join("");
147+
// If the results length is greater than 10, display links to show more results
148+
if (resultsLength > 10) {
149+
resultsHTML += `<hr class="border-divider-light dark:border-divider-dark">`;
150+
resultsHTML += `<ul class="flex flex-wrap gap-1 pt-4 pb-8 justify-center text-sm">`;
151+
for (let i = 1; i <= resultsLength / 10; i++) {
152+
if (i == currentPage) {
153+
resultsHTML += `<li class="flex items-center justify-center">
148154
<a href="/search/?q=${query}&page=${i}" class="pagination-link bg-gray-200 dark:bg-gray-800 dark:text-gray-200">${i}</a>
149155
</li>`;
150-
} else {
151-
resultsHTML += `<li class="flex items-center justify-center">
156+
} else {
157+
resultsHTML += `<li class="flex items-center justify-center">
152158
<a href="/search/?q=${query}&page=${i}" class="pagination-link bg-gray-100 dark:bg-gray-900 dark:text-gray-400">${i}</a>
153159
</li>`;
154-
}
155160
}
156-
resultsHTML += `</ul>`;
157161
}
158-
159-
searchPageResults.innerHTML = resultsHTML;
162+
resultsHTML += `</ul>`;
160163
}
161-
}
162164

163-
searchPageInput.addEventListener("input", (e) => onPageSearch(e));
164-
165-
// Event delegation for tracking link clicks
166-
if (window.heap !== undefined) {
167-
searchPageResults.addEventListener("click", function (event) {
168-
if (event.target.tagName === "A" && event.target.closest(".link")) {
169-
const searchQuery = event.target.getAttribute("data-query");
170-
const resultIndex = event.target.getAttribute("data-index");
171-
const url = new URL(event.target.href);
172-
const properties = {
173-
docs_search_target_path: url.pathname,
174-
docs_search_target_title: event.target.textContent,
175-
docs_search_query_text: searchQuery,
176-
docs_search_target_index: resultIndex,
177-
docs_search_source_path: window.location.pathname,
178-
docs_search_source_title: document.title,
179-
};
180-
heap.track("Docs - Search - Click - Result Link", properties);
181-
}
182-
});
165+
searchPageResults.innerHTML = resultsHTML;
183166
}
184-
</script>
167+
}
168+
169+
searchPageInput.addEventListener("input", (e) => onPageSearch(e));
170+
171+
// Event delegation for tracking link clicks
172+
if (window.heap !== undefined) {
173+
searchPageResults.addEventListener("click", function (event) {
174+
if (event.target.tagName === "A" && event.target.closest(".link")) {
175+
const searchQuery = event.target.getAttribute("data-query");
176+
const resultIndex = event.target.getAttribute("data-index");
177+
const url = new URL(event.target.href);
178+
const properties = {
179+
docs_search_target_path: url.pathname,
180+
docs_search_target_title: event.target.textContent,
181+
docs_search_query_text: searchQuery,
182+
docs_search_target_index: resultIndex,
183+
docs_search_source_path: window.location.pathname,
184+
docs_search_source_title: document.title,
185+
};
186+
heap.track("Docs - Search - Click - Result Link", properties);
187+
}
188+
});
189+
}
190+
</script>
185191
{{ end }}

0 commit comments

Comments
 (0)