128 lines
		
	
	
	
		
			3.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			128 lines
		
	
	
	
		
			3.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 
								 | 
							
									// Based on https://codeberg.org/daudix/duckquill/issues/101#issuecomment-2377169
							 | 
						||
| 
								 | 
							
									let searchSetup = false;
							 | 
						||
| 
								 | 
							
									let fuse;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									async function initIndex() {
							 | 
						||
| 
								 | 
							
										if (searchSetup) return;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										const url = document.getElementById("search-index").textContent;
							 | 
						||
| 
								 | 
							
										const response = await fetch(url);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										const options = {
							 | 
						||
| 
								 | 
							
											includeScore: false,
							 | 
						||
| 
								 | 
							
											includeMatches: true,
							 | 
						||
| 
								 | 
							
											ignoreLocation: true,
							 | 
						||
| 
								 | 
							
											threshold: 0.15,
							 | 
						||
| 
								 | 
							
											keys: [
							 | 
						||
| 
								 | 
							
												{ name: "title", weight: 3 },
							 | 
						||
| 
								 | 
							
												{ name: "description", weight: 2 },
							 | 
						||
| 
								 | 
							
												{ name: "body", weight: 1 }
							 | 
						||
| 
								 | 
							
											]
							 | 
						||
| 
								 | 
							
										};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										fuse = new Fuse(await response.json(), options);
							 | 
						||
| 
								 | 
							
										searchSetup = true;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										console.log("Search index initialized successfully");
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									function toggleSearch() {
							 | 
						||
| 
								 | 
							
										initIndex();
							 | 
						||
| 
								 | 
							
										const searchBar = document.getElementById("search-bar");
							 | 
						||
| 
								 | 
							
										const searchContainer = document.getElementById("search-container");
							 | 
						||
| 
								 | 
							
										const searchResults = document.getElementById("search-results");
							 | 
						||
| 
								 | 
							
										searchContainer.classList.toggle("active");
							 | 
						||
| 
								 | 
							
										searchBar.toggleAttribute("disabled");
							 | 
						||
| 
								 | 
							
										searchBar.focus();
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									function debounce(actual_fn, wait) {
							 | 
						||
| 
								 | 
							
										let timeoutId;
							 | 
						||
| 
								 | 
							
									
							 | 
						||
| 
								 | 
							
										return (...args) => {
							 | 
						||
| 
								 | 
							
											clearTimeout(timeoutId);
							 | 
						||
| 
								 | 
							
									
							 | 
						||
| 
								 | 
							
											timeoutId = setTimeout(() => {
							 | 
						||
| 
								 | 
							
												actual_fn(...args);
							 | 
						||
| 
								 | 
							
											}, wait);
							 | 
						||
| 
								 | 
							
										};
							 | 
						||
| 
								 | 
							
									};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									function initSearch() {
							 | 
						||
| 
								 | 
							
										const searchBar = document.getElementById("search-bar");
							 | 
						||
| 
								 | 
							
										const searchResults = document.getElementById("search-results");
							 | 
						||
| 
								 | 
							
										const searchContainer = document.getElementById("search-container");
							 | 
						||
| 
								 | 
							
										const MAX_ITEMS = 10;
							 | 
						||
| 
								 | 
							
										const MAX_RESULTS = 4;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										let currentTerm = "";
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										searchBar.addEventListener("keyup", (e) => {
							 | 
						||
| 
								 | 
							
											const searchVal = searchBar.value.trim();
							 | 
						||
| 
								 | 
							
											const results = fuse.search(searchVal, { limit: MAX_ITEMS });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											let html = "";
							 | 
						||
| 
								 | 
							
											for (const result of results) {
							 | 
						||
| 
								 | 
							
												html += makeTeaser(result, searchVal);
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											searchResults.innerHTML = html;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											if (html) {
							 | 
						||
| 
								 | 
							
												searchResults.style.display = "flex";
							 | 
						||
| 
								 | 
							
											} else {
							 | 
						||
| 
								 | 
							
												searchResults.style.display = "none";
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										});
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										function makeTeaser(result, searchVal) {
							 | 
						||
| 
								 | 
							
											const TEASER_SIZE = 20;
							 | 
						||
| 
								 | 
							
											let output = `<div class="search-result item"><a class="result-title" href=${result.item.url}>${result.item.title}</a>`;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											for (const match of result.matches) {
							 | 
						||
| 
								 | 
							
												if (match.key === "title") continue;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												const indices = match.indices.sort((a, b) => Math.abs(a[1] - a[0] - searchVal.length) - Math.abs(b[1] - b[0] - searchVal.length)).slice(0, MAX_RESULTS);
							 | 
						||
| 
								 | 
							
												const value = match.value;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												for (const ind of indices) {
							 | 
						||
| 
								 | 
							
													const start = Math.max(0, ind[0] - TEASER_SIZE);
							 | 
						||
| 
								 | 
							
													const end = Math.min(value.length - 1, ind[1] + TEASER_SIZE);
							 | 
						||
| 
								 | 
							
													output += "<span>"
							 | 
						||
| 
								 | 
							
														+ value.substring(start, ind[0])
							 | 
						||
| 
								 | 
							
														+ `<strong>${value.substring(ind[0], ind[1] + 1)}</strong>`
							 | 
						||
| 
								 | 
							
														+ value.substring(ind[1] + 1, end)
							 | 
						||
| 
								 | 
							
														+ "</span>";
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												if (match.indices.length > 4) {
							 | 
						||
| 
								 | 
							
								                    const moreMatchesText = document.getElementById("more-matches-text").textContent;
							 | 
						||
| 
								 | 
							
													output += `<span class="more-matches">${moreMatchesText}</span>`.replace("$MATCHES", `+${match.indices.length - MAX_RESULTS}`);
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											return output + "</div>";
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										/*window.addEventListener("click", function (event) {
							 | 
						||
| 
								 | 
							
											if (searchSetup && searchBar.getAttribute("disabled") === null && !searchContainer.contains(event.target)) {
							 | 
						||
| 
								 | 
							
												toggleSearch();
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										}, { passive: true });*/
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										document.addEventListener("keydown", function(event) {
							 | 
						||
| 
								 | 
							
											if (event.key === "/") {
							 | 
						||
| 
								 | 
							
												event.preventDefault();
							 | 
						||
| 
								 | 
							
												toggleSearch();
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										});
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										document.getElementById("search-toggle").addEventListener("click", toggleSearch);
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									if (document.readyState === "complete" ||
							 | 
						||
| 
								 | 
							
										(document.readyState !== "loading" && !document.documentElement.doScroll))
							 | 
						||
| 
								 | 
							
										initSearch();
							 | 
						||
| 
								 | 
							
									else
							 | 
						||
| 
								 | 
							
										document.addEventListener("DOMContentLoaded", initSearch);
							 |