[gi-docgen: 8/10] feat(search): implement live results




commit a8649efa2801e1157e8b8865353f5ec8e1eb7321
Author: Rom Grk <romgrk cc gmail com>
Date:   Thu Apr 1 10:25:00 2021 -0400

    feat(search): implement live results

 gidocgen/templates/basic/base.html |   4 +-
 gidocgen/templates/basic/search.js | 177 +++++++++++++++++++++----------------
 gidocgen/templates/basic/style.css |   2 +-
 3 files changed, 102 insertions(+), 81 deletions(-)
---
diff --git a/gidocgen/templates/basic/base.html b/gidocgen/templates/basic/base.html
index 677040d..d3446f7 100644
--- a/gidocgen/templates/basic/base.html
+++ b/gidocgen/templates/basic/base.html
@@ -61,8 +61,8 @@ SPDX-License-Identifier: Apache-2.0 OR GPL-3.0-or-later
       {% endif %}
       {% if CONFIG.search_index %}
       <div class="search section">
-        <form id="search-form" method="GET">
-          <input class="search-input" type="text" name="q" placeholder="Search docs"/>
+        <form id="search-form" autocomplete="off">
+          <input id="search-input" type="text" name="do-not-autocomplete" placeholder="Search docs" 
autocomplete="off"/>
         </form>
       </div>
       {% endif %}
diff --git a/gidocgen/templates/basic/search.js b/gidocgen/templates/basic/search.js
index c26efa5..905b019 100644
--- a/gidocgen/templates/basic/search.js
+++ b/gidocgen/templates/basic/search.js
@@ -29,8 +29,15 @@ const QUERY_PATTERN = new RegExp("^(" + QUERY_TYPES.join('|') + ")\\s*:\\s*", 'i
 
 const fzy = window.fzy;
 const searchParams = getSearchParams();
+const refs = {
+    input: null,
+    form: null,
+    search: null,
+    main: null,
+};
 
 let searchIndex = undefined;
+let searchResults = [];
 
 // Exports
 window.onInitSearch = onInitSearch;
@@ -42,107 +49,114 @@ function onInitSearch() {
 }
 
 function onDidLoadSearchIndex(data) {
-    const searchInput = getSearchInput();
-    const searchIndex = new SearchIndex(data)
+    searchIndex = new SearchIndex(data)
 
-    if (searchInput.value === "") {
-        searchInput.value === searchParams.q || "";
-    }
+    refs.input  = document.querySelector("#search-input")
+    refs.form   = document.querySelector("#search-form")
+    refs.search = document.querySelector("#search")
+    refs.main   = document.querySelector("#main")
 
-    function runQuery(query) {
-        const q = matchQuery(query);
-        const docs = searchIndex.searchDocs(q.term, q.type);
-
-        const results = docs.map(function(doc) {
-            return {
-                name: doc.name,
-                type: doc.type,
-                text: getTextForDocument(doc, searchIndex.meta),
-                href: getLinkForDocument(doc),
-                summary: doc.summary,
-            };
-        });
+    attachInputHandlers()
 
-        return results;
+    if (searchParams.q) {
+        search(searchParams.q);
     }
+}
 
-    function search() {
-        const query = searchParams.q;
-        if (searchInput.value === "" && query) {
-            searchInput.value = query;
-        }
-        window.title = "Results for: " + query.user;
-        showResults(query, runQuery(query));
-    }
+function onDidSearch() {
+    const query = refs.input.value
+    if (query)
+        search(refs.input.value)
+    else
+        hideSearchResults()
+}
 
-    window.onpageshow = function() {
-        var query = getQuery(searchParams.q);
-        if (searchInput.value === "" && query) {
-            searchInput.value = query.user;
-        }
-        search();
-    };
+function onDidSubmit(ev) {
+    ev.preventDefault();
+    if (searchResults.length > 0) {
+        window.location.href = searchResults[0].href
+    }
+}
 
-    if (searchParams.q) {
-        search();
+function attachInputHandlers() {
+    if (refs.input.value === "") {
+        refs.input.value === searchParams.q || "";
     }
-};
 
+    refs.input.addEventListener('keydown', debounce(200, onDidSearch))
+    refs.form.addEventListener('submit', onDidSubmit)
+}
 
-/* Rendering */
+/* Searching */
 
-function showSearchResults(search) {
-    if (search === null || typeof search === 'undefined') {
-        search = getSearchElement();
-    }
+function searchQuery(query) {
+    const q = matchQuery(query);
+    const docs = searchIndex.searchDocs(q.term, q.type);
 
-    addClass(main, "hidden");
-    removeClass(search, "hidden");
+    const results = docs.map(function(doc) {
+        return {
+            name: doc.name,
+            type: doc.type,
+            text: getTextForDocument(doc, searchIndex.meta),
+            href: getLinkForDocument(doc),
+            summary: doc.summary,
+        };
+    });
 
+    return results;
 }
 
-function hideSearchResults(search) {
-    if (search === null || typeof search === 'undefined') {
-        search = getSearchElement();
-    }
+function search(query) {
+    searchResults = searchQuery(query)
+    showResults(query, searchResults);
+}
+
+/* Rendering */
 
-    addClass(search, "hidden");
-    removeClass(search, "hidden");
+function showSearchResults() {
+    addClass(refs.main, "hidden");
+    removeClass(refs.search, "hidden");
 }
 
-function addResults(results) {
-    var output = "";
+function hideSearchResults() {
+    addClass(refs.search, "hidden");
+    removeClass(refs.main, "hidden");
+}
 
-    if (results.length > 0) {
-        output += "<table class=\"results\">" +
-                    "<tr><th>Name</th><th>Description</th></tr>";
+function renderResults(results) {
+    if (results.length === 0)
+        return "No results found.";
 
-        results.forEach(function(item) {
-            output += "<tr>" +
-                        "<td class=\"result " + item.type + "\">" +
-                        "<a href=\"" + item.href + "\"><code>" + item.text + "</code></a>" +
-                        "</td>" +
-                        "<td>" + item.summary + "</td>" +
-                        "</tr>";
-        });
+    let output = "";
 
-        output += "</table>";
-    } else {
-        output = "No results found.";
-    }
+    output += "<table class=\"results\">" +
+                "<tr><th>Name</th><th>Description</th></tr>";
+
+    results.forEach(function(item) {
+        output += "<tr>" +
+                    "<td class=\"result " + item.type + "\">" +
+                      "<a href=\"" + item.href + "\"><code>" + item.text + "</code></a>" +
+                    "</td>" +
+                    "<td>" + item.summary + "</td>" +
+                  "</tr>";
+    });
+
+    output += "</table>";
 
     return output;
 }
 
 function showResults(query, results) {
-    const search = getSearchElement();
+    window.title = "Results for: " + query;
+    window.scroll({ top: 0 })
+
     const output =
         "<h1>Results for &quot;" + query + "&quot; (" + results.length + ")</h1>" +
         "<div id=\"search-results\">" +
-            addResults(results) +
+            renderResults(results) +
         "</div>";
 
-    search.innerHTML = output;
+    refs.search.innerHTML = output;
     showSearchResults(search);
 }
 
@@ -250,14 +264,6 @@ function fetchJSON(url, callback) {
     request.send(null);
 }
 
-function getSearchElement() {
-    return document.getElementById("search");
-}
-
-function getSearchInput() {
-    return document.getElementsByClassName("search-input")[0];
-}
-
 function getSearchParams() {
     const params = {};
     window.location.search.substring(1).split('&')
@@ -285,4 +291,19 @@ function matchQuery(input) {
     return { type: type, term: term }
 }
 
+function debounce(delay, fn) {
+  let timeout
+  let savedArgs
+  return function() {
+    const self = this
+    savedArgs = Array.prototype.slice.call(arguments)
+    if (timeout)
+      clearTimeout(timeout)
+    timeout = setTimeout(function() {
+      fn.apply(self, savedArgs)
+      timeout = undefined
+    }, delay)
+  }
+}
+
 })()
diff --git a/gidocgen/templates/basic/style.css b/gidocgen/templates/basic/style.css
index e79cbe5..84915a9 100644
--- a/gidocgen/templates/basic/style.css
+++ b/gidocgen/templates/basic/style.css
@@ -448,7 +448,7 @@ footer {
   border-radius: 50px;
   padding: 6px 12px;
   display: inline-block;
-  font-size: 80%;
+  font-size: 14px;
   box-shadow: inset 0 1px 3px #ddd;
   transition: border .3s linear;
 }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]