mirror of
https://github.com/MarginaliaSearch/MarginaliaSearch.git
synced 2025-02-23 21:18:58 +00:00
(search) Fix typeahead suggestions, as well as improve mobile and desktop UX in small ways.
This commit is contained in:
parent
d530c3096f
commit
96357e9bfd
@ -1,23 +1,47 @@
|
||||
// This sets the data-has-js attribute on the body tag to true, so we can style the page with the assumption that
|
||||
// the browser supports JS. This is a progressive enhancement, so the page will still work without JS.
|
||||
document.getElementsByTagName('body')[0].setAttribute('data-has-js', 'true');
|
||||
|
||||
const registerCloseButton = () => {
|
||||
// Add a button to close the filters for mobile; we do this in js to not pollute the DOM for text-only browsers
|
||||
const closeButton = document.createElement('button');
|
||||
closeButton.setAttribute('id', 'menu-close');
|
||||
closeButton.setAttribute('title', 'Close the menu');
|
||||
closeButton.setAttribute('aria-controls', '#filters');
|
||||
closeButton.innerHTML = 'X';
|
||||
closeButton.onclick = (event) => {
|
||||
document.getElementById('filters').style.display = 'none';
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
document.getElementById('filters').getElementsByTagName('h2')[0].append(closeButton);
|
||||
}
|
||||
|
||||
// Add a button to open the filters for mobile; we do this in js to not pollute the DOM for text-only browsers
|
||||
const button = document.createElement('button');
|
||||
button.setAttribute('id', 'mcfeast');
|
||||
button.setAttribute('aria-controls', '#filters');
|
||||
button.onclick = (event) => {
|
||||
setDisplay(document.getElementById('filters'), 'block');
|
||||
const filtersButton = document.createElement('button');
|
||||
filtersButton.setAttribute('id', 'mcfeast');
|
||||
filtersButton.setAttribute('aria-controls', '#filters');
|
||||
filtersButton.onclick = (event) => {
|
||||
// Defer creation of the close button until the menu is opened. This is needed because the script for creating
|
||||
// the filter button is run early to avoid layout shifts.
|
||||
|
||||
if (document.getElementById('menu-close') === null) {
|
||||
registerCloseButton();
|
||||
}
|
||||
|
||||
document.getElementById('filters').style.display = 'block';
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
button.innerHTML = 'Filters';
|
||||
document.getElementById('search-box').getElementsByTagName('h1')[0].append(button);
|
||||
filtersButton.innerHTML = 'Filters';
|
||||
document.getElementById('search-box').getElementsByTagName('h1')[0].append(filtersButton);
|
||||
|
||||
function setDisplay(element, value) {
|
||||
element.style.display = value;
|
||||
}
|
||||
|
||||
document.getElementById('menu-close').onclick = (event) => {
|
||||
setDisplay(document.getElementById('filters'), 'none');
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
// To prevent the filter menu from being opened when the user hits enter on the search box, we need to add a keydown
|
||||
// handler to the search box that stops the event from propagating. Janky hack, but it works.
|
||||
document.getElementById('query').addEventListener('keydown', e=> {
|
||||
if (e.key === "Enter") {
|
||||
const form = document.getElementById('search-form');
|
||||
form.submit();
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
@ -272,6 +272,7 @@ section.cards {
|
||||
margin-left: 1ch;
|
||||
}
|
||||
|
||||
|
||||
footer {
|
||||
clear: both;
|
||||
|
||||
@ -348,7 +349,7 @@ footer {
|
||||
padding: 0.5ch;
|
||||
background-color: $fg-light;
|
||||
display: grid;
|
||||
grid-template-columns: max-content auto max-content;
|
||||
grid-template-columns: max-content 0 auto max-content;
|
||||
grid-gap: 0.5ch;
|
||||
grid-auto-rows: minmax(1ch, auto);
|
||||
width: 100%;
|
||||
@ -366,6 +367,11 @@ footer {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#suggestions-anchor {
|
||||
margin: -0.5ch; // We need this anchor for the typeahead suggestions, but we don't want it to affect the layout
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
font-family: monospace;
|
||||
font-size: 12pt;
|
||||
@ -375,13 +381,42 @@ footer {
|
||||
color: $fg-dark;
|
||||
}
|
||||
|
||||
button[type="submit"] {
|
||||
input[type="submit"] {
|
||||
font-size: 12pt;
|
||||
border: 1px solid $border-color;
|
||||
background-color: $fg-light;
|
||||
color: $fg-dark;
|
||||
}
|
||||
|
||||
.suggestions {
|
||||
background-color: #fff;
|
||||
padding: .5ch;
|
||||
margin-top: 5.5ch;
|
||||
margin-left: 1ch;
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
width: 300px;
|
||||
border-left: 1px solid #ccc;
|
||||
border-top: 1px solid #ccc;
|
||||
box-shadow: 5px 5px 5px #888;
|
||||
z-index: 10;
|
||||
|
||||
a {
|
||||
display: block;
|
||||
color: #000;
|
||||
font-size: 12pt;
|
||||
font-family: 'fixedsys', monospace, serif;
|
||||
text-decoration: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
a:focus {
|
||||
display: block;
|
||||
background-color: #000;
|
||||
color: #eee;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.filter-toggle-on {
|
||||
@ -519,6 +554,8 @@ footer {
|
||||
padding: 0 0 0 0 !important;
|
||||
max-width: 100%;
|
||||
|
||||
#suggestions-anchor { display: none; } // suggestions are not useful on mobile
|
||||
|
||||
.sidebar-narrow {
|
||||
display: block; // fix for bizarre chrome rendering issue
|
||||
}
|
||||
|
@ -1,86 +1,96 @@
|
||||
|
||||
if(!window.matchMedia("(pointer: coarse)").matches) {
|
||||
query = document.getElementById('query');
|
||||
function setupTypeahead() {
|
||||
const query = document.getElementById('query');
|
||||
query.setAttribute('autocomplete', 'off');
|
||||
timer = null;
|
||||
const queryBox = document.getElementById('suggestions-anchor');
|
||||
let timer = null;
|
||||
|
||||
function fetchSuggestions(e) {
|
||||
if (timer != null) {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
timer = setTimeout(() => {
|
||||
req = new XMLHttpRequest();
|
||||
if (timer != null) {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
timer = setTimeout(() => {
|
||||
const req = new XMLHttpRequest();
|
||||
|
||||
req.onload = rsp => {
|
||||
items = JSON.parse(req.responseText);
|
||||
req.onload = rsp => {
|
||||
let items = JSON.parse(req.responseText);
|
||||
|
||||
var old = document.getElementById('suggestions');
|
||||
if (old != null) old.remove();
|
||||
|
||||
if (items.length == 0) return;
|
||||
|
||||
suggestions = document.createElement('div');
|
||||
suggestions.setAttribute('id', 'suggestions');
|
||||
suggestions.setAttribute('class', 'suggestions');
|
||||
const old = document.getElementById('suggestions');
|
||||
if (old != null) old.remove();
|
||||
|
||||
|
||||
for (i=0;i<items.length;i++) {
|
||||
item = document.createElement('a');
|
||||
item.innerHTML=items[i];
|
||||
item.setAttribute('href', '#')
|
||||
if (items.length === 0) return;
|
||||
|
||||
console.log(items);
|
||||
|
||||
const suggestions = document.createElement('div');
|
||||
suggestions.setAttribute('id', 'suggestions');
|
||||
suggestions.setAttribute('class', 'suggestions');
|
||||
|
||||
|
||||
for (i=0;i<items.length;i++) {
|
||||
item = document.createElement('a');
|
||||
item.innerHTML=items[i];
|
||||
item.setAttribute('href', '#')
|
||||
|
||||
function suggestionClickHandler(e) {
|
||||
query.value = e.target.text;
|
||||
query.focus();
|
||||
document.getElementById('suggestions').remove();
|
||||
e.preventDefault()
|
||||
}
|
||||
item.addEventListener('click', suggestionClickHandler);
|
||||
|
||||
item.addEventListener('keydown', e=> {
|
||||
if (e.key === "ArrowDown") {
|
||||
if (e.target.nextElementSibling != null) {
|
||||
e.target.nextElementSibling.focus();
|
||||
}
|
||||
|
||||
function suggestionClickHandler(e) {
|
||||
query.value = e.target.text;
|
||||
query.focus();
|
||||
document.getElementById('suggestions').remove();
|
||||
e.preventDefault()
|
||||
}
|
||||
item.addEventListener('click', suggestionClickHandler);
|
||||
|
||||
item.addEventListener('keydown', e=> {
|
||||
if (e.key === "ArrowDown") {
|
||||
if (e.target.nextElementSibling != null) {
|
||||
e.target.nextElementSibling.focus();
|
||||
}
|
||||
|
||||
e.preventDefault()
|
||||
else if (e.key === "ArrowUp") {
|
||||
if (e.target.previousElementSibling != null) {
|
||||
e.target.previousElementSibling.focus();
|
||||
}
|
||||
else if (e.key === "ArrowUp") {
|
||||
if (e.target.previousElementSibling != null) {
|
||||
e.target.previousElementSibling.focus();
|
||||
}
|
||||
else {
|
||||
query.focus();
|
||||
}
|
||||
e.preventDefault()
|
||||
}
|
||||
else if (e.key === "Escape") {
|
||||
var suggestions = document.getElementById('suggestions');
|
||||
if (suggestions != null) {
|
||||
suggestions.remove();
|
||||
}
|
||||
else {
|
||||
query.focus();
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
item.addEventListener('keypress', e=> {
|
||||
if (e.key === "Enter") {
|
||||
suggestionClickHandler(e);
|
||||
e.preventDefault()
|
||||
}
|
||||
else if (e.key === "Escape") {
|
||||
var suggestions = document.getElementById('suggestions');
|
||||
if (suggestions != null) {
|
||||
suggestions.remove();
|
||||
}
|
||||
});
|
||||
suggestions.appendChild(item);
|
||||
}
|
||||
document.getElementsByClassName('input')[0].appendChild(suggestions);
|
||||
query.focus();
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
item.addEventListener('keypress', e=> {
|
||||
if (e.key === "Enter") {
|
||||
suggestionClickHandler(e);
|
||||
}
|
||||
});
|
||||
suggestions.appendChild(item);
|
||||
}
|
||||
queryBox.prepend(suggestions);
|
||||
}
|
||||
|
||||
req.open("GET", "/suggest/?partial="+encodeURIComponent(query.value));
|
||||
req.send();
|
||||
}, 250);
|
||||
}
|
||||
req.open("GET", "/suggest/?partial="+encodeURIComponent(query.value));
|
||||
req.send();
|
||||
}, 250);
|
||||
}
|
||||
query.addEventListener("input", fetchSuggestions);
|
||||
query.addEventListener("click", e=> { var suggestions = document.getElementById('suggestions'); if (suggestions != null) suggestions.remove(); });
|
||||
query.addEventListener("click", e=> {
|
||||
const suggestions = document.getElementById('suggestions');
|
||||
if (suggestions != null) {
|
||||
suggestions.remove();
|
||||
}
|
||||
});
|
||||
query.addEventListener("keydown", e => {
|
||||
if (e.key === "ArrowDown") {
|
||||
var suggestions = document.getElementById('suggestions');
|
||||
const suggestions = document.getElementById('suggestions');
|
||||
if (suggestions != null) {
|
||||
suggestions.childNodes[0].focus();
|
||||
}
|
||||
@ -90,7 +100,7 @@ if(!window.matchMedia("(pointer: coarse)").matches) {
|
||||
e.preventDefault()
|
||||
}
|
||||
else if (e.key === "Escape") {
|
||||
var suggestions = document.getElementById('suggestions');
|
||||
const suggestions = document.getElementById('suggestions');
|
||||
if (suggestions != null) {
|
||||
suggestions.remove();
|
||||
}
|
||||
@ -99,3 +109,7 @@ if(!window.matchMedia("(pointer: coarse)").matches) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if(!window.matchMedia("(pointer: coarse)").matches) {
|
||||
setupTypeahead();
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<h2>Filters <button id="menu-close">X</button></h2>
|
||||
<h2>Filters</h2>
|
||||
<ul>
|
||||
{{#with removeJsOption}}
|
||||
<li title="Exclude results with javascript"
|
||||
|
@ -1,15 +1,15 @@
|
||||
<form action="/search" method="get">
|
||||
<form action="/search" method="get" id="search-form">
|
||||
<div id="search-box">
|
||||
<h1>
|
||||
Search The Internet
|
||||
</h1>
|
||||
<input type="text" name="query" placeholder="Search..." value="{{query}}" autocomplete="off">
|
||||
|
||||
<div id="suggestions-anchor"></div>
|
||||
<input type="text" id="query" name="query" placeholder="Search..." value="{{query}}">
|
||||
<input type="hidden" name="js" value="{{js}}">
|
||||
<input type="hidden" name="adtech" value="{{adtech}}">
|
||||
<input type="hidden" name="profile" value="{{profile}}">
|
||||
|
||||
<button type="submit">Search</button>
|
||||
<input type="submit" form="search-form" title="Execute Search" value="Search">
|
||||
</div>
|
||||
</form>
|
||||
<!-- load the mobile customizations script early to avoid flicker -->
|
||||
|
Loading…
Reference in New Issue
Block a user