daydreel-website/public/js/search.js
2026-02-19 14:14:59 +01:00

120 lines
4.1 KiB
JavaScript

function encodeHTML(str) {
return str.replace(/&/g, "&")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
function isValidUrl(url) {
try {
const parsedUrl = new URL(url, window.location.origin);
return parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:";
} catch (e) {
return false;
}
}
let debounceTimeout;
function searchOnChange(evt) {
clearTimeout(debounceTimeout);
debounceTimeout = setTimeout(() => {
performSearch(evt);
}, 300); // Debounce delay of 300ms
}
async function performSearch(evt) {
let searchQuery = evt.target.value.trim().toLowerCase();
if (searchQuery !== "") {
const searchButtonEle = document.querySelectorAll("#search");
if (searchButtonEle.length < 2) {
console.error("Search button elements missing!");
return;
}
let searchButtonPosition;
if (window.innerWidth > 768) {
searchButtonPosition = searchButtonEle[0].getBoundingClientRect();
document.getElementById("search-content").style.width = "500px";
} else {
searchButtonPosition = searchButtonEle[1].getBoundingClientRect();
document.getElementById("search-content").style.width = "300px";
}
document.getElementById("search-content").style.top =
searchButtonPosition.top + 50 + "px";
document.getElementById("search-content").style.left =
searchButtonPosition.left + "px";
try {
// Use the search index URL provided by Hugo template, fallback to /index.json
let indexURL = window.searchIndexURL || '/index.json';
let response = await fetch(indexURL);
if (!response.ok) {
throw new Error("Failed to fetch search data");
}
let searchJson = await response.json();
console.log("Fetched Data:", searchJson); // Debugging log
let searchResults = searchJson.filter((item) => {
if (!item || typeof item !== "object") return false;
if (!item.title && !item.description && !item.content) return false;
return (
(item.title && item.title.toLowerCase().includes(searchQuery)) ||
(item.description && item.description.toLowerCase().includes(searchQuery)) ||
(item.content && item.content.toLowerCase().includes(searchQuery))
);
});
const searchResultsContainer = document.getElementById("search-results");
searchResultsContainer.innerHTML = ""; // Clear previous results
if (searchResults.length > 0) {
searchResults.forEach((item) => {
if (!item.permalink || !isValidUrl(item.permalink)) {
console.warn("Skipping invalid search result:", item);
return;
}
const card = document.createElement("div");
card.className = "card";
const link = document.createElement("a");
link.href = item.permalink; // Safe, since we validated it
const contentDiv = document.createElement("div");
contentDiv.className = "p-3";
const title = document.createElement("h5");
title.textContent = item.title || "Untitled"; // Use textContent to prevent XSS
const description = document.createElement("div");
description.textContent = item.description || "No description available"; // Safe
contentDiv.appendChild(title);
contentDiv.appendChild(description);
link.appendChild(contentDiv);
card.appendChild(link);
searchResultsContainer.appendChild(card);
});
} else {
const noResultsMessage = document.createElement("p");
noResultsMessage.className = "text-center py-3";
noResultsMessage.textContent = `No results found for "${searchQuery}"`;
searchResultsContainer.appendChild(noResultsMessage);
}
document.getElementById("search-content").style.display = "block";
} catch (error) {
console.error("Error fetching search data:", error);
}
} else {
document.getElementById("search-content").style.display = "none";
document.getElementById("search-results").innerHTML = "";
}
}