From 037b540ccb17c1ce4c776274648ae5e02456cbd4 Mon Sep 17 00:00:00 2001 From: Noah Doersing Date: Thu, 25 Mar 2021 21:26:11 +0100 Subject: [PATCH] Enable navigating the search results using the keyboard (addresses part of #1) --- _assets/search.js | 51 +++++++++++++++++++++++++++++++--- _assets/style.css | 18 +++++++++--- _site/assets/search.js | 51 +++++++++++++++++++++++++++++++--- _site/assets/style.css | 18 +++++++++--- _site/index.html | 24 ++++++++-------- _templates/index.template.html | 6 ++-- 6 files changed, 137 insertions(+), 31 deletions(-) diff --git a/_assets/search.js b/_assets/search.js index ae536c8a..8ad1f1b4 100644 --- a/_assets/search.js +++ b/_assets/search.js @@ -1,8 +1,12 @@ const searchInput = document.querySelector("#search_input"); const searchOutput = document.querySelector("#search_output"); -// asynchronously load search "index" (the search box will remain disabled until then) let searchIndex; + +let searchResultsCount = 0; +let searchSelection = -1; + +// asynchronously load search "index" (the search box will remain disabled until then) fetch("search.json") .then(response => response.json()) .then(data => { @@ -46,20 +50,31 @@ function search(query) { } function clearResults() { + searchResultsCount = 0; + searchSelection = -1; + searchOutput.innerHTML = ""; } // render a subset of the search index in the results/output pane function showResults(results) { + searchResultsCount = results.length; + searchSelection = -1; + + let i = 0; const code = results.map(e => { - return `

` + return `` + + `

` + + `` + (e.favorite ? ` ` : ``) + (e.spicy ? ` ` : ``) + ((e.veggie || e.vegan) ? `` : ` `) + (e.vegan ? ` ` : ``) - + `${e.title} ` + + `` + + `${e.title} ` + (e.original_title ? `${e.original_title}` : ``) - + `

`; + + `

` + + ``; }); searchOutput.innerHTML = code.join(""); @@ -73,3 +88,31 @@ searchInput.addEventListener('input', e => { showResults(results); } }); + +// highlight the currently selected search result +function highlightSearchSelection() { + document.querySelectorAll(".searchresult").forEach(e => e.classList.remove("selected")); + if (document.getElementById(`${searchSelection}`)) { + document.getElementById(`${searchSelection}`).classList.add("selected"); + } +} + +// enable keyboard naviation of search results +searchInput.addEventListener('keydown', e => { + if (e.key == "ArrowUp") { + searchSelection = Math.max(-1, searchSelection - 1); + } else if (e.key == "ArrowDown") { + searchSelection = Math.min(searchResultsCount - 1, searchSelection + 1); + } else if (e.key == "Enter") { + if (searchSelection != -1) { + document.getElementById(`${searchSelection}`).click(); + } + } + highlightSearchSelection(); +}) + +// allow, in conjunction with keyboard naviation (otherwise this would be a job for css), highlighting on mouseover +searchOutput.addEventListener('mousemove', e => { + searchSelection = parseInt(e.target.closest("a.searchresult").id); + highlightSearchSelection(); +}); diff --git a/_assets/style.css b/_assets/style.css index 5429fd44..9760d109 100644 --- a/_assets/style.css +++ b/_assets/style.css @@ -75,7 +75,7 @@ h1 em { display: inline-block; } h1 span, -h3 a { /* separate original name after this a bit (but not if it's on another line) */ +h3 span { /* separate original name after this a bit (but not if it's on another line) */ padding-right: 0.4rem; } header ul { @@ -167,6 +167,10 @@ header p { color: darkred; font-size: 0.64em; } +.search .results a { + text-decoration: none; + color: inherit; +} .search .results h3 { border-bottom: 1px solid #ddd; padding: 0.3rem 1rem 0.4rem; @@ -174,10 +178,13 @@ header p { width: calc(100% - 0.5em); background-color: white; } -.search .results h3:last-child { +.search .results a:last-child h3 { border-radius: 0 0 0.2em 0.2em; border-bottom: none; } +.search .results a.selected h3 { + background-color: #f8f8f8; +} .servingsuggestion { width: 48em; @@ -203,7 +210,7 @@ h2 { font-weight: normal; } h3 { - margin: 0.5rem 0 1rem; + margin: 0.5rem 0 1rem; /* larger bottom margin to fix spacing if the last recipe in a category has no description */ font-size: 1.3rem; } h3 a { @@ -227,9 +234,12 @@ h3 .icons img { height: 1em; } h3 + p { - margin-top: -0.5rem; + margin-top: -0.5rem; /* move the description a bit closer to the recipe's title */ font-family: var(--serif); } +h3 + h3 { + margin-top: -0.5rem; /* in the same vein, move the recipe title below a description-less recipe a bit upwards */ +} ul, p { margin: 1rem 0; diff --git a/_site/assets/search.js b/_site/assets/search.js index ae536c8a..d87c679c 100644 --- a/_site/assets/search.js +++ b/_site/assets/search.js @@ -1,8 +1,12 @@ const searchInput = document.querySelector("#search_input"); const searchOutput = document.querySelector("#search_output"); -// asynchronously load search "index" (the search box will remain disabled until then) let searchIndex; + +let searchResultsCount = 0; +let searchSelection = -1; + +// asynchronously load search "index" (the search box will remain disabled until then) fetch("search.json") .then(response => response.json()) .then(data => { @@ -46,20 +50,31 @@ function search(query) { } function clearResults() { + searchResultsCount = 0; + searchSelection = -1; + searchOutput.innerHTML = ""; } // render a subset of the search index in the results/output pane function showResults(results) { + searchResultsCount = results.length; + searchSelection = -1; + + let i = 0; const code = results.map(e => { - return `

` + return `` + + `

` + + `` + (e.favorite ? ` ` : ``) + (e.spicy ? ` ` : ``) + ((e.veggie || e.vegan) ? `` : ` `) + (e.vegan ? ` ` : ``) - + `${e.title} ` + + `` + + `${e.title} ` + (e.original_title ? `${e.original_title}` : ``) - + `

`; + + `

` + + ``; }); searchOutput.innerHTML = code.join(""); @@ -73,3 +88,31 @@ searchInput.addEventListener('input', e => { showResults(results); } }); + +// highlight the currently selected search result +function highlightSearchSelection() { + document.querySelectorAll(".searchresult").forEach(e => e.classList.remove("selected")); + if (document.getElementById(`${searchSelection}`)) { + document.getElementById(`${searchSelection}`).classList.add("selected"); + } +} + +// allow keyboard naviation of search results +searchInput.addEventListener('keydown', e => { + if (e.key == "ArrowUp") { + searchSelection = Math.max(-1, searchSelection - 1); + } else if (e.key == "ArrowDown") { + searchSelection = Math.min(searchResultsCount - 1, searchSelection + 1); + } else if (e.key == "Enter") { + if (searchSelection != -1) { + document.getElementById(`${searchSelection}`).click(); + } + } + highlightSearchSelection(); +}) + +// allow, in conjunction with keyboard naviation (otherwise this would be a job for css), highlighting on mouseover +searchOutput.addEventListener('mousemove', e => { + searchSelection = parseInt(e.target.closest("a.searchresult").id); + highlightSearchSelection(); +}); diff --git a/_site/assets/style.css b/_site/assets/style.css index 5429fd44..9760d109 100644 --- a/_site/assets/style.css +++ b/_site/assets/style.css @@ -75,7 +75,7 @@ h1 em { display: inline-block; } h1 span, -h3 a { /* separate original name after this a bit (but not if it's on another line) */ +h3 span { /* separate original name after this a bit (but not if it's on another line) */ padding-right: 0.4rem; } header ul { @@ -167,6 +167,10 @@ header p { color: darkred; font-size: 0.64em; } +.search .results a { + text-decoration: none; + color: inherit; +} .search .results h3 { border-bottom: 1px solid #ddd; padding: 0.3rem 1rem 0.4rem; @@ -174,10 +178,13 @@ header p { width: calc(100% - 0.5em); background-color: white; } -.search .results h3:last-child { +.search .results a:last-child h3 { border-radius: 0 0 0.2em 0.2em; border-bottom: none; } +.search .results a.selected h3 { + background-color: #f8f8f8; +} .servingsuggestion { width: 48em; @@ -203,7 +210,7 @@ h2 { font-weight: normal; } h3 { - margin: 0.5rem 0 1rem; + margin: 0.5rem 0 1rem; /* larger bottom margin to fix spacing if the last recipe in a category has no description */ font-size: 1.3rem; } h3 a { @@ -227,9 +234,12 @@ h3 .icons img { height: 1em; } h3 + p { - margin-top: -0.5rem; + margin-top: -0.5rem; /* move the description a bit closer to the recipe's title */ font-family: var(--serif); } +h3 + h3 { + margin-top: -0.5rem; /* in the same vein, move the recipe title below a description-less recipe a bit upwards */ +} ul, p { margin: 1rem 0; diff --git a/_site/index.html b/_site/index.html index 71b70169..ed633118 100644 --- a/_site/index.html +++ b/_site/index.html @@ -30,13 +30,13 @@

I Can’t Believe It’s Not a Cookbook!

Bakery

- + - - Vegan Chocolate Cake + + Vegan Chocolate Cake

A pretty basic, but vegan, chocolate cake, easily customizable with nuts (not bolts) or berries.

@@ -44,24 +44,24 @@

Korean Food

- + - - Cheese Buldak + + Cheese Buldak 치즈불닭

Super-spicy chicken tempered with loads of cheese and fresh spring onions. Serve with rice and a light salad – or, better yet, an assortment of side dishes.

- + - - Pan-Fried Perilla Leaves + + Pan-Fried Perilla Leaves 깻잎전

Perilla leaves, stuffed with a pork-based filling, then eggwashed and fried.

@@ -69,13 +69,13 @@

Uncategorized

- + - - Strawberry Smoothie + + Strawberry Smoothie

Great on a hot summer day.

diff --git a/_templates/index.template.html b/_templates/index.template.html index d59a3b58..f5e783d0 100644 --- a/_templates/index.template.html +++ b/_templates/index.template.html @@ -33,13 +33,13 @@

$index_title$

${it.category}

${for(it.recipes)}

- + $if(it.favorite)$$endif$ $if(it.spicy)$$endif$ $if(it.veggie)$$else$$if(it.vegan)$$else$$endif$$endif$ $if(it.vegan)$$endif$ - - ${it.title} + + ${it.title} $if(it.original_title)$$it.original_title$$endif$

$if(it.description)$