Added header template for autocomplete on results view

New header template adds full control over search field on desktop and
mobile, which now allows for autocomplete suggestions on the
results page

Also fixed autocomplete results format, since opensearch requires a
suggestions response of [<original query>, [<suggestion array>]]
This commit is contained in:
Ben Busby 2020-05-23 17:20:33 -06:00
parent 6c31a3eef8
commit 137b0ef8db
9 changed files with 158 additions and 35 deletions

View File

@ -91,9 +91,13 @@ class Filter:
script.decompose() script.decompose()
footer = soup.find('div', id='sfooter') footer = soup.find('div', id='sfooter')
if footer is not None: if footer:
footer.decompose() footer.decompose()
header = soup.find('header')
if header:
header.decompose()
return soup return soup
def remove_ads(self, soup): def remove_ads(self, soup):

View File

@ -15,9 +15,7 @@ DESKTOP_UA = '{}/5.0 (X11; {} x86_64; rv:75.0) Gecko/20100101 {}/75.0'
VALID_PARAMS = ['tbs', 'tbm', 'start', 'near'] VALID_PARAMS = ['tbs', 'tbm', 'start', 'near']
def gen_user_agent(normal_ua): def gen_user_agent(normal_ua, is_mobile):
is_mobile = 'Android' in normal_ua or 'iPhone' in normal_ua
mozilla = random.choice(['Moo', 'Woah', 'Bro', 'Slow']) + 'zilla' mozilla = random.choice(['Moo', 'Woah', 'Bro', 'Slow']) + 'zilla'
firefox = random.choice(['Choir', 'Squier', 'Higher', 'Wire']) + 'fox' firefox = random.choice(['Choir', 'Squier', 'Higher', 'Wire']) + 'fox'
linux = random.choice(['Win', 'Sin', 'Gin', 'Fin', 'Kin']) + 'ux' linux = random.choice(['Win', 'Sin', 'Gin', 'Fin', 'Kin']) + 'ux'
@ -66,8 +64,9 @@ def gen_query(query, args, config, near_city=None):
class Request: class Request:
def __init__(self, normal_ua, language='lang_en'): def __init__(self, normal_ua, language='lang_en'):
self.modified_user_agent = gen_user_agent(normal_ua)
self.language = language self.language = language
self.mobile = 'Android' in normal_ua or 'iPhone' in normal_ua
self.modified_user_agent = gen_user_agent(normal_ua, self.mobile)
def __getitem__(self, name): def __getitem__(self, name):
return getattr(self, name) return getattr(self, name)

View File

@ -96,7 +96,7 @@ def autocomplete():
if not q: if not q:
return jsonify({'results': []}) return jsonify({'results': []})
return jsonify({'results': g.user_request.autocomplete(q)}) return jsonify([q, g.user_request.autocomplete(q)])
@app.route('/search', methods=['GET', 'POST']) @app.route('/search', methods=['GET', 'POST'])
@ -132,7 +132,14 @@ def search():
else: else:
formatted_results = content_filter.clean(dirty_soup) formatted_results = content_filter.clean(dirty_soup)
return render_template('display.html', query=urlparse.unquote(q), response=formatted_results) return render_template(
'display.html',
query=urlparse.unquote(q),
response=formatted_results,
search_header=render_template(
'header.html',
q=urlparse.unquote(q),
mobile=g.user_request.mobile))
@app.route('/config', methods=['GET', 'POST']) @app.route('/config', methods=['GET', 'POST'])

55
app/static/css/header.css Normal file
View File

@ -0,0 +1,55 @@
header {
font-family: Roboto,HelveticaNeue,Arial,sans-serif;
font-size: 14px;
line-height: 20px;
color: #3C4043;
word-wrap: break-word;
}
.logo-link, .logo-letter {
text-decoration: none !important;
letter-spacing: -1px;
text-align: center;
border-radius: 2px 0 0 0;
}
.mobile-logo {
font: 22px/36px Futura, Arial, sans-serif;
padding-left: 5px;
}
.logo-div {
letter-spacing: -1px;
text-align: center;
font: 22pt Futura, Arial, sans-serif;
padding: 10px 0 5px 0;
height: 37px;
font-smoothing: antialiased;
}
.search-div {
border-radius: 8px 8px 0 0;
box-shadow: 0 1px 6px rgba(32, 33, 36, 0.18);
margin-top: 10px;
}
.search-form {
height: 39px;
display: flex;
width: 100%;
}
.search-input {
background: none;
margin: 2px 4px 2px 8px;
display: block;
font-size: 16px;
padding: 0 0 0 8px;
flex: 1;
height: 35px;
outline: none;
border: none;
width: 100%;
-webkit-tap-highlight-color: rgba(0,0,0,0);
overflow: hidden;
}

View File

@ -1,11 +1,29 @@
function autocomplete(searchInput, autocompleteResults) { const handleUserInput = searchBar => {
let xhrRequest = new XMLHttpRequest();
xhrRequest.open("POST", "/autocomplete");
xhrRequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhrRequest.onload = function() {
if (xhrRequest.readyState === 4 && xhrRequest.status !== 200) {
alert("Error fetching autocomplete results");
return;
}
// Fill autocomplete with fetched results
let autocompleteResults = JSON.parse(xhrRequest.responseText);
autocomplete(searchBar, autocompleteResults[1]);
};
xhrRequest.send('q=' + searchBar.value);
};
const autocomplete = (searchInput, autocompleteResults) => {
let currentFocus; let currentFocus;
searchInput.addEventListener("input", function () { searchInput.addEventListener("input", function () {
let autocompleteList, autocompleteItem, i, val = this.value; let autocompleteList, autocompleteItem, i, val = this.value;
closeAllLists(); closeAllLists();
if (!val) { if (!val || !autocompleteResults) {
return false; return false;
} }
@ -47,7 +65,7 @@ function autocomplete(searchInput, autocompleteResults) {
} }
}); });
function addActive(suggestion) { const addActive = suggestion => {
if (!suggestion || !suggestion[currentFocus]) return false; if (!suggestion || !suggestion[currentFocus]) return false;
removeActive(suggestion); removeActive(suggestion);
@ -55,25 +73,26 @@ function autocomplete(searchInput, autocompleteResults) {
if (currentFocus < 0) currentFocus = (suggestion.length - 1); if (currentFocus < 0) currentFocus = (suggestion.length - 1);
suggestion[currentFocus].classList.add("autocomplete-active"); suggestion[currentFocus].classList.add("autocomplete-active");
} };
function removeActive(suggestion) { const removeActive = suggestion => {
for (let i = 0; i < suggestion.length; i++) { for (let i = 0; i < suggestion.length; i++) {
suggestion[i].classList.remove("autocomplete-active"); suggestion[i].classList.remove("autocomplete-active");
} }
} };
function closeAllLists(el) { const closeAllLists = el => {
let suggestions = document.getElementsByClassName("autocomplete-items"); let suggestions = document.getElementsByClassName("autocomplete-items");
for (let i = 0; i < suggestions.length; i++) { for (let i = 0; i < suggestions.length; i++) {
if (el !== suggestions[i] && el !== searchInput) { if (el !== suggestions[i] && el !== searchInput) {
suggestions[i].parentNode.removeChild(suggestions[i]); suggestions[i].parentNode.removeChild(suggestions[i]);
} }
} }
} };
// Close lists and search when user selects a suggestion // Close lists and search when user selects a suggestion
document.addEventListener("click", function (e) { document.addEventListener("click", function (e) {
closeAllLists(e.target); closeAllLists(e.target);
document.getElementById("search-form").submit();
}); });
} };

View File

@ -1,21 +1,3 @@
const handleUserInput = searchBar => {
let xhrRequest = new XMLHttpRequest();
xhrRequest.open("POST", "/autocomplete");
xhrRequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhrRequest.onload = function() {
if (xhrRequest.readyState === 4 && xhrRequest.status !== 200) {
alert("Error fetching autocomplete results");
return;
}
// Fill autocomplete with fetched results
let autocompleteResults = JSON.parse(xhrRequest.responseText);
autocomplete(searchBar, autocompleteResults["results"]);
};
xhrRequest.send('q=' + searchBar.value);
};
const setupSearchLayout = () => { const setupSearchLayout = () => {
// Setup search field // Setup search field
const searchBar = document.getElementById("search-bar"); const searchBar = document.getElementById("search-bar");

View File

@ -5,9 +5,13 @@
<link rel="search" href="/opensearch.xml" type="application/opensearchdescription+xml" title="Whoogle Search"> <link rel="search" href="/opensearch.xml" type="application/opensearchdescription+xml" title="Whoogle Search">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="referrer" content="no-referrer"> <meta name="referrer" content="no-referrer">
<script type="text/javascript" src="/static/js/autocomplete.js"></script>
<link rel="stylesheet" href="/static/css/search.css">
<link rel="stylesheet" href="/static/css/header.css">
<title>{{ query }} - Whoogle Search</title> <title>{{ query }} - Whoogle Search</title>
</head> </head>
<body> <body>
{{ search_header|safe }}
{{ response|safe }} {{ response|safe }}
</body> </body>
</html> </html>

53
app/templates/header.html Normal file
View File

@ -0,0 +1,53 @@
{% if mobile %}
<header>
<div class="bz1lBb">
<form class="Pg70bf" id="search-form" method="POST">
<a class="logo-link mobile-logo"
href="/"
style="display:flex; justify-content:center; align-items:center; color:#685e79; font-size:18px; ">
<span class="V6gwVd">Wh</span><span class="iWkuvd">o</span><span class="cDrQ7">o</span><span
class="V6gwVd">g</span><span class="ntlR9">l</span><span
class="iWkuvd tJ3Myc">e</span>
</a>
<div class="H0PQec" style="width: 100%;">
<div class="sbc esbc autocomplete">
<input id="search-bar" autocapitalize="none" autocomplete="off" class="noHIxc" name="q"
spellcheck="false" type="text" value="{{ q }}">
<div class="sc"></div>
</div>
</div>
</form>
</div>
</header>
{% else %}
<header>
<div class="logo-div">
<a class="logo-link" href="/">
<span class="V6gwVd logo-letter">Wh</span><span class="iWkuvd logo-letter">o</span><span
class="cDrQ7 logo-letter">o</span><span class="V6gwVd logo-letter">g</span><span
class="ntlR9 logo-letter">l</span><span class="iWkuvd tJ3Myc logo-letter">e</span>
</a>
</div>
<div class="search-div">
<form id="search-form" class="search-form" id="sf" method="POST">
<div class="autocomplete" style="width: 100%; flex: 1">
<div style="width: 100%; display: flex">
<input id="search-bar" autocapitalize="none" autocomplete="off" class="noHIxc" name="q"
spellcheck="false" type="text" value="{{ q }}">
<div class="sc"></div>
</div>
</div>
</form>
</div>
</header>
{% endif %}
<script>
const searchBar = document.getElementById("search-bar");
searchBar.addEventListener("keyup", function (event) {
if (event.keyCode !== 13) {
handleUserInput(searchBar);
}
});
</script>

View File

@ -28,7 +28,7 @@
<body id="main" style="display: none; background-color: {{ bg }}"> <body id="main" style="display: none; background-color: {{ bg }}">
<div class="search-container"> <div class="search-container">
<img class="logo" src="/static/img/logo.png"> <img class="logo" src="/static/img/logo.png">
<form action="/search" method="{{ request_type }}"> <form id="search-form" action="/search" method="{{ request_type }}">
<div class="search-fields"> <div class="search-fields">
<div class="autocomplete"> <div class="autocomplete">
<input type="text" name="q" id="search-bar" autofocus="autofocus"> <input type="text" name="q" id="search-bar" autofocus="autofocus">