diff --git a/_includes/body-landing.html b/_includes/body-landing.html index 0b7366f45f..492781f433 100644 --- a/_includes/body-landing.html +++ b/_includes/body-landing.html @@ -34,7 +34,7 @@
-
+ -
-
-
+
diff --git a/_includes/navigation.html b/_includes/navigation.html index 0fa2816fcf..56fb21f1f4 100644 --- a/_includes/navigation.html +++ b/_includes/navigation.html @@ -4,12 +4,10 @@
-
+ - -
-
-
+ +
diff --git a/_scss/_landing.scss b/_scss/_landing.scss index c660f2f453..27d8b7b23c 100644 --- a/_scss/_landing.scss +++ b/_scss/_landing.scss @@ -99,8 +99,8 @@ body.landing { } #autocompleteResults { - left: 10px; - right: 10px; + left: 14px; + right: 14px; width: unset; @include before-lg-width { diff --git a/_scss/_night-mode.scss b/_scss/_night-mode.scss index c96eec113d..ea5a3b497b 100755 --- a/_scss/_night-mode.scss +++ b/_scss/_night-mode.scss @@ -89,16 +89,21 @@ body.night { .toc-nav i.fa { color: $body-text-night; } - div#autocompleteResults { + #autocompleteResults { background: $bg-search-results-night; border: 1px solid $black; - color: $white; + //color: $white; } - .autocompleteList li { - color: #b4c3d2; - } - div#autocompleteResults span { - color: #b7a4de; + .autocompleteResult { + &.selected { + background-color: #1f262e; + } + li { + color: #b4c3d2; + } + span { + color: #b7a4de; + } } /* for google results styles - forgive us css gods */ diff --git a/_scss/_utilities.scss b/_scss/_utilities.scss index 17a6e1c55e..69fa19cb8c 100755 --- a/_scss/_utilities.scss +++ b/_scss/_utilities.scss @@ -135,73 +135,59 @@ input[type=text] { * autocompleteResults ********************************************************* */ -div#autocompleteResults { +#autocompleteResults { display: none; /* toggled through javascript */ background: #E6F5FD; border: 1px solid #eee; box-shadow: 1px 2px 2px rgba(0, 0, 0, 0.28); - padding: 20px 0 15px 0; margin: 10px 0 0 0; position: absolute; width: 600px; z-index: 9999; } -ul.autocompleteList { - list-style: none; - max-width: 100%; - - @include before-lg-width { - padding: 0; - } -} - -.autocompleteList li { - padding: 5px 0 7px 0; - max-width: 100%; -} - -div#autocompleteResults span { - background: transparent!important; - color: #518cad; -} - -.autocompleteTitle { - font-weight: bold; - font-size: large; -} - -.autocompleteSelected { - background-color: #f5f5f5; -} - -.autocompleteList { - list-style-type: none; - width: 400px; -/* commented out 0 padding to allow inherit padding, search results on autocompleteList were getting smashed up against left margin due to this */ -/* padding: 0; */ - margin-bottom: 0; -} - -.autoCompleteResult { +.autocompleteResult { border-bottom: 1px solid rgba(203, 205, 209, 0.4); + cursor: pointer; padding: 15px; - - &:hover { - cursor: pointer; + &.selected { + background-color: #f5f5f5; } -} -#autocompleteShowAll { - padding: 20px 15px; -} + ul { + list-style: none; + margin: 0 5px 0 5px; + padding-inline-start: 0; -.autocompleteList li { - width: 380px; - border: 0; - padding-right: 20px; - margin: 0; + @include before-lg-width { + padding: 0; + } + } + + li { + border: 0; + margin: 0; + max-width: 100%; + padding: 5px 0 7px 0; + line-height: normal; + } + + .title { + font-weight: bold; + } + + .keywords { + font-size: 12px; + .glyphicon { + padding-right: 10px + } + } + + span { + background: transparent; + color: #518cad; + } } @media print { diff --git a/js/search.js b/js/search.js index b6dddb16cd..164334a855 100644 --- a/js/search.js +++ b/js/search.js @@ -1,199 +1,115 @@ -var metadata, glossary; -var autoCompleteShowing = false; -var displayingAutcompleteResults = new Array(); -var autoCompleteShowingID = 0; -var lastSearch = ""; -var autoCompleteResultLimit = 3; -var results = new Array(); -var scoreForTitleMatch = 10; -var scoreForURLMatch = 5; -var scoreForKeywordMatch = 3; -var scoreForDescriptionMatch = 1 -function addResult(topic, matchesTitle, matchesDescription, matchesURL, matchesKeywords) -{ - var matchScore = (matchesTitle * scoreForTitleMatch) + (matchesDescription * scoreForDescriptionMatch) + (matchesURL * scoreForURLMatch) + (matchesKeywords * scoreForKeywordMatch); - if (matchScore > 0) - { - var resultIndex = results.length; - results[resultIndex] = new Array(); - results[resultIndex].topic = topic; - results[resultIndex].score = matchScore; - } -} -function loadPage(url) -{ - window.location.replace(url); - window.location.href = url; -} -$(document).on("keypress", function(event) { - if (event.keyCode == 13) { - if(autoCompleteShowing) event.preventDefault(); +const maxResults = 3, titleWeight = 10, urlWeight = 5, keywordWeight = 3, descriptionWeight = 1 +let searchVal = "", pages = [] + +function handleKeyNav(/* KeyboardEvent */ e) { + let row = _(".autocompleteResult.selected") + switch (e.key) { + case "ArrowUp": + if (row && row.previousElementSibling) { + row.classList.remove("selected") + row = row.previousElementSibling + row.classList.add("selected") + } + break; + case "ArrowDown": + if (!row) { + // pick the first one + row = _(".autocompleteResult") + } else if (row.nextElementSibling) { + row.classList.remove("selected") + row = row.nextElementSibling + } + if (row) { + row.classList.add("selected") + } + break; + case "Enter": + e.preventDefault(); + if (!row || row.id === "autocompleteShowAll") { + // "see all" is selected or no autocomplete result selected + window.location.href = "/search/?q=" + e.target.value; + } else { + // an autocomplete result is selected + row.click() + } + break; } -}); +} -function highlightMe(inputTxt,keyword) -{ - inputTxt = String(inputTxt); - simpletext = new RegExp("(" + keyword + ")","gi"); - return inputTxt.replace(simpletext, "$1") +function matches(input, search) { + return String(input).toUpperCase().split(search.toUpperCase()).length - 1; } -function matches(inputTxt,searchTxt) -{ - var subs = inputTxt.split(searchTxt); - return subs.length - 1; -} -function bindSearch() -{ - $("#st-search-input").on('keyup change', function(e) { - e = e || window.event; - if (autoCompleteShowing) - { - if (e.keyCode == '38') { - // up arrow - if (autoCompleteShowingID > -1) - { - // go up a result - $("#autoCompleteResult" + autoCompleteShowingID).removeClass("autocompleteSelected"); - autoCompleteShowingID = autoCompleteShowingID - 1; - $("#autoCompleteResult" + autoCompleteShowingID).addClass("autocompleteSelected"); - $("#autocompleteShowAll").removeClass("autocompleteSelected"); - } else { - // de-selection auto-complete; reverting to raw search - $("#autoCompleteResult0").removeClass("autocompleteSelected"); - autoCompleteShowingID = -1; - } - } else if (e.keyCode == '40') { - // down arrow - if (autoCompleteShowingID < (displayingAutcompleteResults.length - 1)) - { - // go down to the next result - $("#autoCompleteResult" + autoCompleteShowingID).removeClass("autocompleteSelected"); - autoCompleteShowingID = autoCompleteShowingID + 1; - $("#autoCompleteResult" + autoCompleteShowingID).addClass("autocompleteSelected"); - } else { - // select "See all results..." and go no further - $("#autoCompleteResult" + autoCompleteShowingID).removeClass("autocompleteSelected"); - $("#autocompleteShowAll").addClass("autocompleteSelected"); - autoCompleteShowingID = autoCompleteResultLimit; - } - } else if (e.keyCode == '13') { - // return key - e.preventDefault(); - if (autoCompleteShowingID==autoCompleteResultLimit || autoCompleteShowingID == -1 || autoCompleteShowing == false) - { - // "see all" is selected or they don't have an autocomplete result selected - loadPage("/search/?q=" + $("#st-search-input").val()); - } else { - // an autocomplete result is selected - loadPage(pages[displayingAutcompleteResults[autoCompleteShowingID]].url); - } - } - //console.log('autoCompleteShowingID:',autoCompleteShowingID,'displayingAutcompleteResults[id]:',displayingAutcompleteResults[autoCompleteShowingID],'pages[id].url:',pages[displayingAutcompleteResults[autoCompleteShowingID]].url); + +function handleSearch(/* KeyboardEvent */ e) { + if (e.target.value === searchVal) { + // no new search + return } - var searchVal = $("#st-search-input").val(); - if (lastSearch != searchVal) - { - displayingAutcompleteResults = []; - results = []; - var uppercaseSearchVal = searchVal.toUpperCase(); - //console.log("input changed: ",$("#st-search-input").val()); - - if (searchVal.length > 2) { - for (i=0;i 0) console.log(uppercaseSearchVal,'matches',thisPage.title,titleMatches,'times'); - if (thisPage.description != null) { - matchesDescription = matches(String(thisPage.description).toUpperCase(),uppercaseSearchVal); - } - if (thisPage.url != null) { - matchesURL = matches(String(thisPage.url).toUpperCase(),uppercaseSearchVal); - } - if (thisPage.keywords != null) { - matchesKeywords = matches(String(thisPage.keywords).toUpperCase(),uppercaseSearchVal); - } - addResult(i, matchesTitle, matchesDescription, matchesURL, matchesKeywords); + searchVal = e.target.value + let results = []; + if (searchVal.length > 2) { + for (let i = 0; i < pages.length; i++) { + // search url, description, title, and keywords for search input + const p = pages[i]; + if (!p.title) { + continue + } + let score = (matches(p.title, searchVal) * titleWeight) + if (p.description != null) { + score += (matches(p.description, searchVal) * descriptionWeight) + } + if (p.url != null) { + score += (matches(p.url, searchVal) * urlWeight) + } + if (p.keywords != null) { + score += (matches(p.keywords, searchVal) * keywordWeight) + } + if (score > 0) { + results.push({ "topic": i, "score": score }); + } } - results.sort(function(a,b) { - return b.score - a.score; - }); - } - if (results.length > 0) - { - autoCompleteShowingID = -1; - var resultsShown = 0; - var resultsOutput = new Array(); - resultsOutput.push("
") - //console.log(results); - for (i=0; i < autoCompleteResultLimit && i < results.length; i++) - { - //console.log(i, "of", autoCompleteResultLimit, "is underway"); - displayingAutcompleteResults.push(results[i].topic); //log results to global array - resultsOutput.push("
"); - resultsOutput.push("
    "); - resultsOutput.push("
  • ") - resultsOutput.push("" + highlightMe(pages[results[i].topic].title,searchVal) + ""); - resultsOutput.push("
  • "); - resultsOutput.push("
  • ") - resultsOutput.push(highlightMe(pages[results[i].topic].url,searchVal)); - resultsOutput.push("
  • "); - /* - resultsOutput.push("
  • ") - resultsOutput.push("Breadcrumb: " + breadcrumbString(pages[results[i]].url)); - resultsOutput.push("
  • "); - */ - if (pages[results[i].topic].keywords) - { - resultsOutput.push("
  • ") - resultsOutput.push("Keywords: " + highlightMe(pages[results[i].topic].keywords,searchVal) + ""); - resultsOutput.push("
  • "); - } - if (pages[results[i].topic].description) - { - resultsOutput.push("
  • ") - resultsOutput.push("Description: " + highlightMe(pages[results[i].topic].description,searchVal)); - resultsOutput.push("
  • "); - } - resultsOutput.push("
"); - resultsOutput.push("
") - resultsShown++; - } - var resultsShownText = (resultsShown > 1) ? resultsShown + " of " + results.length + " docs" : "doc"; - resultsOutput.push("") - resultsOutput.push("
"); - $("#autocompleteResults").css("display","block"); - $("#autocompleteResults").html(resultsOutput.join("")); - autoCompleteShowing = true; - } else { - $("#autocompleteResults").css("display","none"); - $("#autocompleteResults").html(""); - autoCompleteShowing = false; - } - lastSearch = searchVal; - } // if searchVal != lastSearch - }); -} - -function queryString() -{ - var vars = [], hash; - var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&'); - for(var i = 0; i < hashes.length; i++) - { - hash = hashes[i].split('='); - vars.push(hash[0]); - vars[hash[0]] = hash[1]; } - return vars; -} + let rows = [] + if (results.length > 0) { + results.sort((a, b) => b.score - a.score); + const match = new RegExp(`(${searchVal})`, "gi"); + const highlight = function (/* String */ content) { + return content.replace(match, "$1") + } + for (let i = 0; i < maxResults && i < results.length; i++) { + const p = pages[results[i].topic]; + rows.push(`
") + } + let shown = Math.min(results.length, maxResults) + rows.push(`
`) + } -let pages = [] + const out = _("#autocompleteResults") + if (out) { + out.innerHTML = rows.join(""); + let shown = Math.min(results.length, maxResults) + out.style.display = shown === 0 ? "none" : "block" + } +} ready(() => { - getJSON( "/js/metadata.json", data => pages = data); - bindSearch() + getJSON( "/js/metadata.json", function(data) { + pages = data + const input = _("#st-search-input") + if (/* HTMLInputElement */ input) { + input.form.addEventListener('submit', (e) => e.preventDefault()); + input.addEventListener('keyup', handleKeyNav, {capture: true }) + input.addEventListener('keyup', debounce(handleSearch, 100), {capture: true }) + } + }); }) diff --git a/js/theme-switcher.js b/js/theme-switcher.js index bce65e16cd..1d2f0cdf01 100644 --- a/js/theme-switcher.js +++ b/js/theme-switcher.js @@ -10,6 +10,14 @@ function getJSON(url, fn) { xhr.send(); } +// throttle / debounce events. taken from https://programmingwithmosh.com/javascript/javascript-throttle-and-debounce-patterns/ +function debounce(fn, msec) { + let id; + return function(...args) { + clearTimeout(id); id = setTimeout(() => fn.apply(this, args), msec); + } +} + const darkMode = window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches const selectedTheme = window.localStorage ? localStorage.getItem("theme") : null; diff --git a/search.md b/search.md index 5ade9ff4ec..cbe130bac1 100644 --- a/search.md +++ b/search.md @@ -10,29 +10,29 @@ skip_read_time: true
@@ -41,16 +41,17 @@ skip_read_time: true