(function () { "use strict"; // TOGGLE DARK/LIGHT MODE function initThemeToggle() { const rootHtml = document.documentElement; const toggleThemeBtn = document.getElementById("theme-toggle"); // If no saved theme, determine user preference, otherwise default to light const savedTheme = localStorage.getItem("theme"); const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches; const initialTheme = savedTheme ?? (prefersDark ? "dark" : "light"); function setTheme(theme) { const isDarkMode = theme === "dark"; // toggleThemeBtn dataset comes with translated labels for site's language const label = isDarkMode ? toggleThemeBtn.dataset.labelLight : toggleThemeBtn.dataset.labelDark; rootHtml.setAttribute("data-theme", theme); toggleThemeBtn.setAttribute("aria-label", label); // display handled by CSS } // Apply initial theme setTheme(initialTheme); // Change theme on click and save user's choice toggleThemeBtn.addEventListener("click", () => { const newTheme = rootHtml.getAttribute("data-theme") === "dark" ? "light" : "dark"; setTheme(newTheme); localStorage.setItem("theme", newTheme); }); } // SEARCH function initSearch() { const input = document.getElementById("search-input"); const resetBtn = document.getElementById("search-reset"); const resultsCount = document.querySelector(".search-results__count"); const resultsList = document.querySelector(".search-results__list"); const template = document.getElementById("search-result-template"); // Only initialize search on the search page if (!input || !template) return; let allPosts = []; let searchTimeout; // Get JSON file with data of all posts fetch(input.dataset.indexUrl ?? "/index.json") .then((res) => res.json()) .then((data) => allPosts = data) .catch((err) => console.error("Search index.json failed to load", err)); function clearResults() { resultsCount.hidden = true; resultsList.innerHTML = ""; resultsList.hidden = true; } function renderSearchResults(matches) { clearResults(); // Display how many results found for query resultsCount.hidden = false; resultsCount.querySelector("#search-results-number").textContent = String(matches.length ?? 0); // No posts matching query found if (!matches.length) return; // Hydrate post-card list item(s) from the template with JSON data matches.forEach((post) => { const li = template.content.firstElementChild.cloneNode(true); const link = li.querySelector(".post-card__link"); link.href = post.url; link.innerHTML = post.title; const date = li.querySelector(".post-card__publish-date"); date.textContent = new Date(post.date).toLocaleDateString(); date.setAttribute("datetime", post.date); const summary = li.querySelector(".post-card__summary"); if (summary) summary.textContent = post.summary; const tagsList = li.querySelector(".post-card__tags-list"); if (tagsList) { tagsList.innerHTML = ""; if (post.tags && post.tags.length) { post.tags.slice(0, 3).forEach((tag) => { const liTag = document.createElement("li"); liTag.className = "post-card__tags-item"; liTag.textContent = `#${tag}`; tagsList.appendChild(liTag); }); if (post.tags.length > 3) { const more = document.createElement("li"); more.className = "post-card__tags-item post-card__tags-more"; more.textContent = `+${post.tags.length - 3}`; tagsList.appendChild(more); } } } resultsList.appendChild(li); resultsList.hidden = false; }); } // Filter all posts for ones matching the user's search query function searchPosts(query) { const normalizedQuery = query.trim().toLowerCase(); // Only search if user entered at least 3 chars if (normalizedQuery.length < 3) { clearResults(); return; } const matches = allPosts.filter((post) => ( post.title.toLowerCase().includes(normalizedQuery) || (post.summary && post.summary.toLowerCase().includes(normalizedQuery)) )); // At least 1 post matching query found renderSearchResults(matches); } input.addEventListener("input", (event) => { // Debounce search clearTimeout(searchTimeout); searchTimeout = setTimeout(() => { searchPosts(event.target.value); }, 300); }); resetBtn.addEventListener("click", () => { input.value = ""; input.focus(); clearResults(); }); // Focus input on page load - it's what the user is here for input.focus(); } initThemeToggle(); initSearch(); })();