Refactor CSS for search results. Add support for keyboard control to full search results page
This commit is contained in:
parent
b90d7b9d1e
commit
74b8040d39
5 changed files with 71 additions and 49 deletions
|
@ -68,25 +68,35 @@ function debouncer(interval, callback) {
|
|||
form.classList.remove("focus");
|
||||
});
|
||||
|
||||
function moveFocus(delta) {
|
||||
function moveFocus(element, delta) {
|
||||
const focusIndexText = document.activeElement.getAttribute("data-focusindex");
|
||||
if (!focusIndexText) return;
|
||||
const currentIndex = parseInt(focusIndexText, 10);
|
||||
const nextIndex = currentIndex + delta;
|
||||
const nextIndex = focusIndexText ? parseInt(focusIndexText, 10) + delta : 0;
|
||||
|
||||
const candidate = form.querySelector("[data-focusindex=\"" + nextIndex + "\"]");
|
||||
const candidate = element.querySelector("[data-focusindex=\"" + nextIndex + "\"]");
|
||||
if (candidate) candidate.focus();
|
||||
}
|
||||
|
||||
form.addEventListener('keydown', function (ev) {
|
||||
function focusControl(element, ev) {
|
||||
if (ev.key === 'ArrowUp') {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
moveFocus(-1);
|
||||
moveFocus(element, -1);
|
||||
} else if (ev.key === 'ArrowDown') {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
moveFocus(1);
|
||||
moveFocus(element, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (let element of document.querySelectorAll(".keyboard-focus-control")) {
|
||||
const captureElement = element;
|
||||
element.addEventListener('keydown', ev => focusControl(captureElement, ev));
|
||||
}
|
||||
|
||||
const defaultKeyboardFocusControl = document.querySelector(".default-keyboard-focus-control");
|
||||
if (defaultKeyboardFocusControl) {
|
||||
document.addEventListener('keydown', ev => {
|
||||
focusControl(defaultKeyboardFocusControl, ev);
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
|
|
@ -6,6 +6,10 @@
|
|||
url('amatic-sc-v9-latin-regular.woff') format('woff');
|
||||
}
|
||||
|
||||
.prototype {
|
||||
display: none;
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: "Apple Garamond", "Baskerville",
|
||||
"Times New Roman", "Droid Serif", "Times",
|
||||
|
@ -292,13 +296,29 @@ article ul.search-results {
|
|||
}
|
||||
.search-result {
|
||||
border: 1px solid #ccc;
|
||||
padding: 8px 16px;
|
||||
margin-bottom: 16px;
|
||||
padding: 0;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.search-result .title {
|
||||
font-weight: bold;
|
||||
}
|
||||
.snippet {
|
||||
white-space: pre-line;
|
||||
}
|
||||
.search-result p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.search-result a {
|
||||
display: block;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
padding: 8px;
|
||||
}
|
||||
.search-result a:hover, .search-result a:focus {
|
||||
background: #0074D9;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.search {
|
||||
text-align: center;
|
||||
|
@ -347,33 +367,10 @@ article ul.search-results {
|
|||
}
|
||||
|
||||
.live-results .search-result {
|
||||
padding: 0;
|
||||
border-top: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.live-results .search-result li {
|
||||
padding: 0;
|
||||
}
|
||||
.live-results .search-result a {
|
||||
display: block;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
padding: 8px;
|
||||
}
|
||||
.live-results .search-result a:hover, .live-results .search-result a:focus {
|
||||
background: #0074D9;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.live-results .search-result .title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.prototype {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 630px) {
|
||||
.search {
|
||||
text-align: right;
|
||||
|
|
|
@ -128,29 +128,36 @@ impl Resource for SearchResource {
|
|||
}
|
||||
|
||||
fn get(self: Box<Self>) -> ResponseFuture {
|
||||
#[derive(BartDisplay)]
|
||||
#[template="templates/search.html"]
|
||||
struct Template<'a> {
|
||||
query: &'a str,
|
||||
hits: Vec<models::SearchResult>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct JsonResponse<'a> {
|
||||
query: &'a str,
|
||||
hits: &'a [models::SearchResult],
|
||||
}
|
||||
|
||||
impl models::SearchResult {
|
||||
fn link(&self) -> String {
|
||||
struct Hit<'a> {
|
||||
index: usize,
|
||||
slug: &'a str,
|
||||
title: &'a str,
|
||||
snippet: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> Hit<'a> {
|
||||
fn link(&self) -> &'a str {
|
||||
if self.slug == "" {
|
||||
".".to_owned()
|
||||
"."
|
||||
} else {
|
||||
self.slug.clone()
|
||||
self.slug
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(BartDisplay)]
|
||||
#[template="templates/search.html"]
|
||||
struct Template<'a> {
|
||||
query: &'a str,
|
||||
hits: &'a [Hit<'a>],
|
||||
}
|
||||
|
||||
// TODO: Show a search "front page" when no query is given:
|
||||
let query = self.query.as_ref().map(|x| x.clone()).unwrap_or("".to_owned());
|
||||
|
||||
|
@ -172,7 +179,15 @@ impl Resource for SearchResource {
|
|||
title: "Search",
|
||||
body: &Template {
|
||||
query: self.query.as_ref().map(|x| &**x).unwrap_or(""),
|
||||
hits: data,
|
||||
hits: &data.iter()
|
||||
.enumerate()
|
||||
.map(|(i, result)| Hit {
|
||||
index: i,
|
||||
slug: &result.slug,
|
||||
title: &result.title,
|
||||
snippet: &result.snippet,
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
},
|
||||
}.to_string())),
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<link href="_assets/style-{{style_css_checksum()}}.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<form class=search action=_search method=GET>
|
||||
<form class="search keyboard-focus-control" action=_search method=GET>
|
||||
<input data-focusindex="0" type=search name=q placeholder=search autocomplete=off>
|
||||
<ul class="live-results search-results">
|
||||
</ul>
|
||||
|
|
|
@ -7,9 +7,9 @@
|
|||
{{#hits?}}
|
||||
<p>Search results for the query <b>{{query}}</b>:</p>
|
||||
|
||||
<ul class="search-results">
|
||||
<ul class="search-results default-keyboard-focus-control">
|
||||
{{#hits}}
|
||||
<li class="search-result"><a href="{{.link()}}">{{.title}}</a> – <span class="snippet">{{.snippet}}</span></li>
|
||||
<li class="search-result"><a data-focusindex="{{.index}}" class="link" href="{{.link()}}"><p class="title">{{.title}}</p><p class="snippet">{{.snippet}}</p></a></li>
|
||||
{{/hits}}
|
||||
</ul>
|
||||
{{/hits}}
|
||||
|
|
Loading…
Reference in a new issue