From 9aa6a18be2eae0d84c7897470a46ede19d5ac191 Mon Sep 17 00:00:00 2001
From: Aaron Pham <29749331+aarnphm@users.noreply.github.com>
Date: Thu, 01 Feb 2024 20:56:42 +0000
Subject: [PATCH] fix(search): improve more general usability (closes #781) (#782)

---
 quartz/components/scripts/search.inline.ts |   66 ++++++++++++++++++++++-----------
 1 files changed, 44 insertions(+), 22 deletions(-)

diff --git a/quartz/components/scripts/search.inline.ts b/quartz/components/scripts/search.inline.ts
index 170a8f0..43332a6 100644
--- a/quartz/components/scripts/search.inline.ts
+++ b/quartz/components/scripts/search.inline.ts
@@ -122,6 +122,9 @@
     if (preview) {
       removeAllChildren(preview)
     }
+    if (searchLayout) {
+      searchLayout.style.opacity = "0"
+    }
 
     searchType = "basic" // reset search type after closing
   }
@@ -135,6 +138,8 @@
     searchBar?.focus()
   }
 
+  let currentHover: HTMLInputElement | null = null
+
   async function shortcutHandler(e: HTMLElementEventMap["keydown"]) {
     if (e.key === "k" && (e.ctrlKey || e.metaKey) && !e.shiftKey) {
       e.preventDefault()
@@ -150,51 +155,61 @@
       if (searchBar) searchBar.value = "#"
     }
 
-    const resultCards = document.getElementsByClassName("result-card")
+    if (currentHover) {
+      currentHover.classList.remove("focus")
+    }
 
     // If search is active, then we will render the first result and display accordingly
     if (!container?.classList.contains("active")) return
-    else if (results?.contains(document.activeElement)) {
-      const active = document.activeElement as HTMLInputElement
-      await displayPreview(active)
-      if (e.key === "Enter") {
+    else if (e.key === "Enter") {
+      // If result has focus, navigate to that one, otherwise pick first result
+      if (results?.contains(document.activeElement)) {
+        const active = document.activeElement as HTMLInputElement
+        if (active.classList.contains("no-match")) return
+        await displayPreview(active)
         active.click()
+      } else {
+        const anchor = document.getElementsByClassName("result-card")[0] as HTMLInputElement | null
+        if (!anchor || anchor?.classList.contains("no-match")) return
+        await displayPreview(anchor)
+        anchor.click()
       }
-    } else {
-      const anchor = resultCards[0] as HTMLInputElement | null
-      await displayPreview(anchor)
-      if (e.key === "Enter") {
-        anchor?.click()
-      }
-    }
-
-    if (e.key === "ArrowUp" || (e.shiftKey && e.key === "Tab")) {
+    } else if (e.key === "ArrowUp" || (e.shiftKey && e.key === "Tab")) {
       e.preventDefault()
       if (results?.contains(document.activeElement)) {
         // If an element in results-container already has focus, focus previous one
-        const currentResult = document.activeElement as HTMLInputElement | null
+        const currentResult = currentHover
+          ? currentHover
+          : (document.activeElement as HTMLInputElement | null)
         const prevResult = currentResult?.previousElementSibling as HTMLInputElement | null
         currentResult?.classList.remove("focus")
         await displayPreview(prevResult)
         prevResult?.focus()
+        currentHover = prevResult
       }
     } else if (e.key === "ArrowDown" || e.key === "Tab") {
       e.preventDefault()
       // The results should already been focused, so we need to find the next one.
       // The activeElement is the search bar, so we need to find the first result and focus it.
       if (!results?.contains(document.activeElement)) {
-        const firstResult = resultCards[0] as HTMLInputElement | null
+        const firstResult = currentHover
+          ? currentHover
+          : (document.getElementsByClassName("result-card")[0] as HTMLInputElement | null)
         const secondResult = firstResult?.nextElementSibling as HTMLInputElement | null
         firstResult?.classList.remove("focus")
         await displayPreview(secondResult)
         secondResult?.focus()
+        currentHover = secondResult
       } else {
         // If an element in results-container already has focus, focus next one
-        const active = document.activeElement as HTMLInputElement | null
+        const active = currentHover
+          ? currentHover
+          : (document.activeElement as HTMLInputElement | null)
         active?.classList.remove("focus")
         const nextResult = active?.nextElementSibling as HTMLInputElement | null
         await displayPreview(nextResult)
         nextResult?.focus()
+        currentHover = nextResult
       }
     }
   }
@@ -282,15 +297,17 @@
 
     async function onMouseEnter(ev: MouseEvent) {
       // Actually when we hover, we need to clean all highlights within the result childs
+      if (!ev.target) return
       for (const el of document.getElementsByClassName(
         "result-card",
       ) as HTMLCollectionOf<HTMLElement>) {
         el.classList.remove("focus")
         el.blur()
       }
-      const target = ev.target as HTMLAnchorElement
-      target.classList.add("focus")
+      const target = ev.target as HTMLInputElement
       await displayPreview(target)
+      currentHover = target
+      currentHover.classList.remove("focus")
     }
 
     async function onMouseLeave(ev: MouseEvent) {
@@ -320,7 +337,7 @@
 
     removeAllChildren(results)
     if (finalResults.length === 0) {
-      results.innerHTML = `<a class="result-card">
+      results.innerHTML = `<a class="result-card no-match">
                     <h3>No results.</h3>
                     <p>Try another search term?</p>
                 </a>`
@@ -329,8 +346,13 @@
     }
     // focus on first result, then also dispatch preview immediately
     if (results?.firstElementChild) {
-      results?.firstElementChild?.classList.add("focus")
-      await displayPreview(results?.firstElementChild as HTMLElement)
+      const firstChild = results.firstElementChild as HTMLElement
+      if (firstChild.classList.contains("no-match")) {
+        removeAllChildren(preview as HTMLElement)
+      } else {
+        firstChild.classList.add("focus")
+        await displayPreview(firstChild)
+      }
     }
   }
 

--
Gitblit v1.10.0