From ae0247be24e2c5d0dea7e4d6311e9a945057da58 Mon Sep 17 00:00:00 2001 From: Cory Dransfeldt Date: Thu, 29 May 2025 10:03:35 -0700 Subject: [PATCH] feat(search.html): update to disable and show loading state w/load more button; improve fuzzy search + debounce --- package-lock.json | 4 +- package.json | 2 +- src/pages/static/search.html | 114 +++++++++++++++++++---------------- 3 files changed, 64 insertions(+), 56 deletions(-) diff --git a/package-lock.json b/package-lock.json index 15a008a..637a0ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "coryd.dev", - "version": "8.0.4", + "version": "8.1.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "coryd.dev", - "version": "8.0.4", + "version": "8.1.4", "license": "MIT", "dependencies": { "minisearch": "^7.1.2", diff --git a/package.json b/package.json index 718d58d..1c12f46 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "coryd.dev", - "version": "8.0.4", + "version": "8.1.4", "description": "The source for my personal site. Built using 11ty (and other tools).", "type": "module", "engines": { diff --git a/src/pages/static/search.html b/src/pages/static/search.html index ad49127..b486a13 100644 --- a/src/pages/static/search.html +++ b/src/pages/static/search.html @@ -84,7 +84,8 @@ description: Search through posts and other content on my site. searchOptions: { fields: ["title", "tags"], prefix: true, - fuzzy: 0.1, + fuzzy: 0.3, + combineWith: "OR", boost: { title: 5, tags: 2, description: 1 }, }, }); @@ -94,12 +95,11 @@ description: Search through posts and other content on my site. const $input = document.querySelector(".search__form--input"); const $results = document.querySelector(".search__results"); const $loadMoreButton = document.querySelector(".search__load-more"); - const $typeCheckboxes = document.querySelectorAll( - '.search__form--type input[type="checkbox"]', - ); + const $typeCheckboxes = document.querySelectorAll('.search__form--type input[type="checkbox"]'); $form.removeAttribute("action"); $form.removeAttribute("method"); + if ($fallback) $fallback.remove(); const PAGE_SIZE = 10; @@ -107,7 +107,7 @@ description: Search through posts and other content on my site. let currentResults = []; let total = 0; let debounceTimeout; - + let isLoading = false; const parseMarkdown = (markdown) => markdown ? markdown @@ -117,7 +117,6 @@ description: Search through posts and other content on my site. .replace(/\n/g, "
") .replace(/[#*_~`]/g, "") : ""; - const truncateDescription = (markdown, maxLength = 225) => { const plainText = new DOMParser().parseFromString(parseMarkdown(markdown), "text/html") @@ -126,12 +125,29 @@ description: Search through posts and other content on my site. ? `${plainText.substring(0, maxLength)}...` : plainText; }; - const renderSearchResults = (results) => { const resultHTML = results .map( ({ title, url, description, type, total_plays }) => ` -
  • +
  • +

    + ${title} + ${type === "artist" && total_plays > 0 ? ` ${total_plays} plays` : ""} +

    +

    ${truncateDescription(description)}

    +
  • + `, + ) + .join(""); + + $results.innerHTML = resultHTML || '
  • No results found.
  • '; + $results.style.display = "block"; + }; + const appendSearchResults = (results) => { + const newResultsHTML = results + .map( + ({ title, url, description, type, total_plays }) => ` +
  • ${title} ${ @@ -140,45 +156,50 @@ description: Search through posts and other content on my site. : "" }

    -

    ${truncateDescription(description)}

    -
  • - `, +

    ${truncateDescription(description)}

    + + `, ) .join(""); - - $results.innerHTML = - resultHTML || - '
  • No results found.
  • '; - $results.style.display = "block"; + $results.insertAdjacentHTML("beforeend", newResultsHTML); }; - + const getSelectedTypes = () => Array.from($typeCheckboxes).filter((cb) => cb.checked).map((cb) => cb.value); const loadSearchIndex = async (query, types, page) => { + isLoading = true; + $loadMoreButton.disabled = true; + $loadMoreButton.textContent = "Loading..."; + try { const typeQuery = types.join(","); const response = await fetch( - `https://www.coryd.dev/api/search.php?q=${query}&type=${typeQuery}&page=${page}&pageSize=${PAGE_SIZE}`, + `https://www.coryd.dev/api/search.php?q=${encodeURIComponent( + query, + )}&type=${typeQuery}&page=${page}&pageSize=${PAGE_SIZE}`, ); const data = await response.json(); + total = data.total || 0; const formattedResults = (data.results || []).map((item) => ({ ...item, id: item.result_id, })); + miniSearch.removeAll(); miniSearch.addAll(formattedResults); + return formattedResults; } catch (error) { console.error("Error fetching search data:", error); + return []; + } finally { + isLoading = false; + $loadMoreButton.disabled = false; + $loadMoreButton.textContent = "Load More"; } }; - const getSelectedTypes = () => - Array.from($typeCheckboxes) - .filter((cb) => cb.checked) - .map((cb) => cb.value); - const updateSearchResults = (results) => { if (currentPage === 1) { renderSearchResults(results); @@ -189,58 +210,45 @@ description: Search through posts and other content on my site. currentPage * PAGE_SIZE < total ? "block" : "none"; }; - const appendSearchResults = (results) => { - const newResultsHTML = results - .map( - ({ title, url, description, type, total_plays }) => ` -
  • -

    - ${title} - ${ - type === "artist" && total_plays > 0 - ? ` ${total_plays} plays` - : "" - } -

    -

    ${truncateDescription(description)}

    -
  • - `, - ) - .join(""); - $results.insertAdjacentHTML("beforeend", newResultsHTML); - }; - const handleSearch = async () => { const query = $input.value.trim(); + if (!query) { renderSearchResults([]); + $loadMoreButton.style.display = "none"; + return; } + $results.innerHTML = '
  • Searching...
  • '; + $results.style.display = "block"; + $loadMoreButton.style.display = "none"; + const results = await loadSearchIndex(query, getSelectedTypes(), 1); + currentResults = results; currentPage = 1; + updateSearchResults(results); }; $input.addEventListener("input", () => { clearTimeout(debounceTimeout); - debounceTimeout = setTimeout(handleSearch, 150); + debounceTimeout = setTimeout(handleSearch, 300); }); - $typeCheckboxes.forEach((cb) => - cb.addEventListener("change", handleSearch), - ); + $typeCheckboxes.forEach((cb) => cb.addEventListener("change", handleSearch)); $loadMoreButton.addEventListener("click", async () => { + if (isLoading) return; + currentPage++; - const nextResults = await loadSearchIndex( - $input.value.trim(), - getSelectedTypes(), - currentPage, - ); + + const nextResults = await loadSearchIndex($input.value.trim(), getSelectedTypes(), currentPage); + currentResults = [...currentResults, ...nextResults]; + updateSearchResults(nextResults); }); })();