mirror of
https://git.lolcat.ca/lolcat/4get.git
synced 2024-11-08 17:43:07 -05:00
added autocomplete
This commit is contained in:
parent
71a61304b0
commit
edc42ea35d
9 changed files with 594 additions and 53 deletions
225
api/v1/ac.php
Normal file
225
api/v1/ac.php
Normal file
|
@ -0,0 +1,225 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
new autocomplete();
|
||||||
|
|
||||||
|
class autocomplete{
|
||||||
|
|
||||||
|
public function __construct(){
|
||||||
|
|
||||||
|
header("Content-Type: application/json");
|
||||||
|
|
||||||
|
$this->scrapers = [
|
||||||
|
"brave" => "https://search.brave.com/api/suggest?q={searchTerms}",
|
||||||
|
"ddg" => "https://duckduckgo.com/ac/?q={searchTerms}&type=list",
|
||||||
|
"yandex" => "https://suggest.yandex.com/suggest-ff.cgi?part={searchTerms}&uil=en&v=3&sn=5&lr=21276&yu=4861394161661655015",
|
||||||
|
"google" => "https://www.google.com/complete/search?client=mobile-gws-lite&q={searchTerms}",
|
||||||
|
"qwant" => "https://api.qwant.com/v3/suggest/?q={searchTerms}&client=opensearch",
|
||||||
|
"yep" => "https://api.yep.com/ac/?query={searchTerms}",
|
||||||
|
"marginalia" => "https://search.marginalia.nu/suggest/?partial={searchTerms}",
|
||||||
|
"yt" => "https://suggestqueries-clients6.youtube.com/complete/search?client=youtube&q={searchTerms}",
|
||||||
|
"sc" => "https://api-v2.soundcloud.com/search/queries?q={searchTerms}&client_id=iMxZgT5mfGstBj8GWJbYMvpzelS8ne0E&limit=10&offset=0&linked_partitioning=1&app_version=1693487844&app_locale=en"
|
||||||
|
];
|
||||||
|
|
||||||
|
/*
|
||||||
|
Sanitize input
|
||||||
|
*/
|
||||||
|
if(!isset($_GET["s"])){
|
||||||
|
|
||||||
|
$this->do404("Missing search(s) parameter");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(is_string($_GET["s"]) === false){
|
||||||
|
|
||||||
|
$this->do404("Invalid search(s) parameter");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(strlen($_GET["s"]) > 500){
|
||||||
|
|
||||||
|
$this->do404("Search(s) exceeds the 500 char length");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(
|
||||||
|
isset($_GET["scraper"]) &&
|
||||||
|
is_string($_GET["scraper"]) === false
|
||||||
|
){
|
||||||
|
|
||||||
|
$_GET["scraper"] = "brave"; // default option
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Get $scraper
|
||||||
|
*/
|
||||||
|
if(!isset($_GET["scraper"])){
|
||||||
|
|
||||||
|
if(isset($_COOKIE["scraper_ac"])){
|
||||||
|
|
||||||
|
$scraper = $_COOKIE["scraper_ac"];
|
||||||
|
}else{
|
||||||
|
|
||||||
|
$scraper = "brave"; // default option
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
|
||||||
|
$scraper = $_GET["scraper"];
|
||||||
|
}
|
||||||
|
|
||||||
|
if($scraper == "disabled"){
|
||||||
|
|
||||||
|
// this shouldnt happen, but let's handle it anyways
|
||||||
|
$this->doempty();
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure it exists
|
||||||
|
if(!isset($this->scrapers[$scraper])){
|
||||||
|
|
||||||
|
$scraper = "brave"; // default option
|
||||||
|
}
|
||||||
|
|
||||||
|
// return results
|
||||||
|
|
||||||
|
switch($scraper){
|
||||||
|
|
||||||
|
case "google":
|
||||||
|
case "yt":
|
||||||
|
// handle google cause they want to be a special snowflake :(
|
||||||
|
$js = $this->get($this->scrapers[$scraper], $_GET["s"]);
|
||||||
|
|
||||||
|
preg_match(
|
||||||
|
'/\((\[.*\])\)/',
|
||||||
|
$js,
|
||||||
|
$js
|
||||||
|
);
|
||||||
|
|
||||||
|
if(!isset($js[1])){
|
||||||
|
|
||||||
|
$this->doempty();
|
||||||
|
}
|
||||||
|
|
||||||
|
$js = json_decode($js[1]);
|
||||||
|
$json = [];
|
||||||
|
|
||||||
|
foreach($js[1] as $item){
|
||||||
|
|
||||||
|
$json[] = strip_tags($item[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode(
|
||||||
|
[
|
||||||
|
$_GET["s"],
|
||||||
|
$json
|
||||||
|
]
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "sc":
|
||||||
|
// soundcloud
|
||||||
|
$js = $this->get($this->scrapers[$scraper], $_GET["s"]);
|
||||||
|
|
||||||
|
$js = json_decode($js, true);
|
||||||
|
|
||||||
|
if(!isset($js["collection"])){
|
||||||
|
|
||||||
|
$this->doempty();
|
||||||
|
}
|
||||||
|
|
||||||
|
$json = [];
|
||||||
|
foreach($js["collection"] as $item){
|
||||||
|
|
||||||
|
$json[] = $item["query"];
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode(
|
||||||
|
[
|
||||||
|
$_GET["s"],
|
||||||
|
$json
|
||||||
|
]
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "marginalia":
|
||||||
|
$json = $this->get($this->scrapers[$scraper], $_GET["s"]);
|
||||||
|
|
||||||
|
$json = json_decode($json, true);
|
||||||
|
if($json === null){
|
||||||
|
|
||||||
|
|
||||||
|
$this->doempty();
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode(
|
||||||
|
[
|
||||||
|
$_GET["s"],
|
||||||
|
$json
|
||||||
|
]
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// if it respects the openSearch protocol
|
||||||
|
$json = json_decode($this->get($this->scrapers[$scraper], $_GET["s"]), true);
|
||||||
|
|
||||||
|
echo json_encode(
|
||||||
|
[
|
||||||
|
$_GET["s"],
|
||||||
|
$json[1] // ensure it contains valid key 0
|
||||||
|
]
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function get($url, $query){
|
||||||
|
|
||||||
|
$curlproc = curl_init();
|
||||||
|
|
||||||
|
$url = str_replace("{searchTerms}", urlencode($query), $url);
|
||||||
|
|
||||||
|
curl_setopt($curlproc, CURLOPT_URL, $url);
|
||||||
|
|
||||||
|
curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
|
||||||
|
curl_setopt($curlproc, CURLOPT_HTTPHEADER,
|
||||||
|
["User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/116.0",
|
||||||
|
"Accept: application/json, text/javascript, */*; q=0.01",
|
||||||
|
"Accept-Language: en-US,en;q=0.5",
|
||||||
|
"Accept-Encoding: gzip",
|
||||||
|
"DNT: 1",
|
||||||
|
"Connection: keep-alive",
|
||||||
|
"Sec-Fetch-Dest: empty",
|
||||||
|
"Sec-Fetch-Mode: cors",
|
||||||
|
"Sec-Fetch-Site: same-site"]
|
||||||
|
);
|
||||||
|
|
||||||
|
curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2);
|
||||||
|
curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true);
|
||||||
|
curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30);
|
||||||
|
curl_setopt($curlproc, CURLOPT_TIMEOUT, 30);
|
||||||
|
|
||||||
|
$data = curl_exec($curlproc);
|
||||||
|
|
||||||
|
if(curl_errno($curlproc)){
|
||||||
|
|
||||||
|
throw new Exception(curl_error($curlproc));
|
||||||
|
}
|
||||||
|
|
||||||
|
curl_close($curlproc);
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function do404($error){
|
||||||
|
|
||||||
|
echo json_encode(["error" => $error]);
|
||||||
|
die();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function doempty(){
|
||||||
|
|
||||||
|
echo json_encode(
|
||||||
|
[
|
||||||
|
$_GET["s"],
|
||||||
|
[]
|
||||||
|
]
|
||||||
|
);
|
||||||
|
die();
|
||||||
|
}
|
||||||
|
}
|
|
@ -574,8 +574,6 @@ class brave{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
echo "test";
|
|
||||||
|
|
||||||
if($rating !== null){
|
if($rating !== null){
|
||||||
|
|
||||||
$table["Rating"] = $rating;
|
$table["Rating"] = $rating;
|
||||||
|
|
|
@ -1616,6 +1616,7 @@ class google{
|
||||||
$imgvl
|
$imgvl
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if(isset($imgvl[1])){
|
||||||
$imgvl = $imgvl[1];
|
$imgvl = $imgvl[1];
|
||||||
|
|
||||||
$params["async"] = "_id:islrg_c,_fmt:html";
|
$params["async"] = "_id:islrg_c,_fmt:html";
|
||||||
|
@ -1633,6 +1634,7 @@ class google{
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return $out;
|
return $out;
|
||||||
}
|
}
|
||||||
|
|
|
@ -288,7 +288,7 @@ class sc{
|
||||||
|
|
||||||
if(count($description) != 0){
|
if(count($description) != 0){
|
||||||
|
|
||||||
$description = $count . " songs. " . implode(", ", $description);
|
$description = trim($count . " songs. " . implode(", ", $description));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(
|
if(
|
||||||
|
@ -320,7 +320,7 @@ class sc{
|
||||||
|
|
||||||
$out["playlist"][] = [
|
$out["playlist"][] = [
|
||||||
"title" => $item["title"],
|
"title" => $item["title"],
|
||||||
"description" => $description,
|
"description" => $this->limitstrlen($description),
|
||||||
"author" => [
|
"author" => [
|
||||||
"name" => $item["user"]["username"],
|
"name" => $item["user"]["username"],
|
||||||
"url" => $item["user"]["permalink_url"],
|
"url" => $item["user"]["permalink_url"],
|
||||||
|
@ -385,13 +385,14 @@ class sc{
|
||||||
"\n",
|
"\n",
|
||||||
wordwrap(
|
wordwrap(
|
||||||
str_replace(
|
str_replace(
|
||||||
"\n",
|
["\n\r", "\r\n", "\n", "\r"],
|
||||||
" ",
|
" ",
|
||||||
$text
|
$text
|
||||||
),
|
),
|
||||||
300,
|
300,
|
||||||
"\n"
|
"\n"
|
||||||
)
|
),
|
||||||
|
2
|
||||||
)[0];
|
)[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
75
settings.php
75
settings.php
|
@ -58,6 +58,56 @@ $settings = [
|
||||||
[
|
[
|
||||||
"name" => "Scrapers to use",
|
"name" => "Scrapers to use",
|
||||||
"settings" => [
|
"settings" => [
|
||||||
|
[
|
||||||
|
"description" => "Autocomplete<br><i>Picking <div class=\"code-inline\">Auto</div> changes the source dynamically depending of the page's scraper<br>Picking <div class=\"code-inline\">Disabled</div> disables this feature</i>",
|
||||||
|
"parameter" => "scraper_ac",
|
||||||
|
"options" => [
|
||||||
|
[
|
||||||
|
"value" => "disabled",
|
||||||
|
"text" => "Disabled"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"value" => "auto",
|
||||||
|
"text" => "Auto"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"value" => "brave",
|
||||||
|
"text" => "Brave"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"value" => "ddg",
|
||||||
|
"text" => "DuckDuckGo"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"value" => "yandex",
|
||||||
|
"text" => "Yandex"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"value" => "google",
|
||||||
|
"text" => "Google"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"value" => "qwant",
|
||||||
|
"text" => "Qwant"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"value" => "yep",
|
||||||
|
"text" => "Yep"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"value" => "marginalia",
|
||||||
|
"text" => "Marginalia"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"value" => "yt",
|
||||||
|
"text" => "YouTube"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"value" => "sc",
|
||||||
|
"text" => "SoundCloud"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
],
|
||||||
[
|
[
|
||||||
"description" => "Web",
|
"description" => "Web",
|
||||||
"parameter" => "scraper_web",
|
"parameter" => "scraper_web",
|
||||||
|
@ -183,8 +233,13 @@ $settings = [
|
||||||
if($_POST){
|
if($_POST){
|
||||||
|
|
||||||
$loop = &$_POST;
|
$loop = &$_POST;
|
||||||
}else{
|
}elseif(count($_GET) !== 0){
|
||||||
|
|
||||||
|
// redirect user to front page
|
||||||
|
$loop = &$_GET;
|
||||||
|
header("Location: /");
|
||||||
|
|
||||||
|
}else{
|
||||||
// refresh cookie dates
|
// refresh cookie dates
|
||||||
$loop = &$_COOKIE;
|
$loop = &$_COOKIE;
|
||||||
}
|
}
|
||||||
|
@ -245,7 +300,7 @@ echo
|
||||||
'<head>' .
|
'<head>' .
|
||||||
'<meta http-equiv="Content-Type" content="text/html;charset=utf-8">' .
|
'<meta http-equiv="Content-Type" content="text/html;charset=utf-8">' .
|
||||||
'<title>Settings</title>' .
|
'<title>Settings</title>' .
|
||||||
'<link rel="stylesheet" href="/static/style.css?v3">' .
|
'<link rel="stylesheet" href="/static/style.css?v4">' .
|
||||||
'<meta name="viewport" content="width=device-width,initial-scale=1">' .
|
'<meta name="viewport" content="width=device-width,initial-scale=1">' .
|
||||||
'<meta name="robots" content="index,follow">' .
|
'<meta name="robots" content="index,follow">' .
|
||||||
'<link rel="icon" type="image/x-icon" href="/favicon.ico">' .
|
'<link rel="icon" type="image/x-icon" href="/favicon.ico">' .
|
||||||
|
@ -260,14 +315,14 @@ $left =
|
||||||
'By clicking <div class="code-inline">Update settings!</div>, a plaintext <div class="code-inline">key=value</div> cookie will be stored on your browser. When selecting a default setting, the parameter is removed from your cookies.';
|
'By clicking <div class="code-inline">Update settings!</div>, a plaintext <div class="code-inline">key=value</div> cookie will be stored on your browser. When selecting a default setting, the parameter is removed from your cookies.';
|
||||||
|
|
||||||
$c = count($_COOKIE);
|
$c = count($_COOKIE);
|
||||||
|
$code = "";
|
||||||
|
|
||||||
if($c !== 0){
|
if($c !== 0){
|
||||||
|
|
||||||
$left .=
|
$left .=
|
||||||
'<br><br>Your current cookie looks like this:' .
|
'<br><br>Your current cookie looks like this:' .
|
||||||
'<div class="code">';
|
'<div class="code">';
|
||||||
|
|
||||||
$code = "";
|
|
||||||
|
|
||||||
$ca = 0;
|
$ca = 0;
|
||||||
foreach($_COOKIE as $key => $value){
|
foreach($_COOKIE as $key => $value){
|
||||||
|
|
||||||
|
@ -326,17 +381,23 @@ $left .=
|
||||||
'</div>' .
|
'</div>' .
|
||||||
'<div class="settings-submit">' .
|
'<div class="settings-submit">' .
|
||||||
'<input type="submit" value="Update settings!">' .
|
'<input type="submit" value="Update settings!">' .
|
||||||
'<a href="../">< Return to main page</a>' .
|
'<a href="../">< Return to front page</a>' .
|
||||||
'</div>' .
|
'</div>' .
|
||||||
'</form>';
|
'</form>';
|
||||||
|
|
||||||
echo
|
if(count($_GET) === 0){
|
||||||
|
|
||||||
|
echo
|
||||||
$frontend->load(
|
$frontend->load(
|
||||||
"search.html",
|
"search.html",
|
||||||
[
|
[
|
||||||
"class" => "",
|
"class" => "",
|
||||||
"right-left" => "",
|
"right-left" =>
|
||||||
|
'<div class="infobox"><h2>Preference link</h2>Follow this link to auto-apply all cookies. Useful if your browser clears out cookies after a browsing session. Following this link will redirect you to the front page, unless no settings are set.<br><br>' .
|
||||||
|
'<a href="settings' . rtrim("?" . str_replace("; ", "&", $code), "?") . '">Bookmark me!</a>' .
|
||||||
|
'</div>',
|
||||||
"right-right" => "",
|
"right-right" => "",
|
||||||
"left" => $left
|
"left" => $left
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
264
static/client.js
264
static/client.js
|
@ -660,15 +660,16 @@ function changeimage(event){
|
||||||
centerpopup();
|
centerpopup();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
Shortcuts
|
|
||||||
*/
|
|
||||||
var searchbox_wrapper = document.getElementsByClassName("searchbox");
|
var searchbox_wrapper = document.getElementsByClassName("searchbox");
|
||||||
|
|
||||||
if(searchbox_wrapper.length !== 0){
|
if(searchbox_wrapper.length !== 0){
|
||||||
|
|
||||||
searchbox_wrapper = searchbox_wrapper[0];
|
searchbox_wrapper = searchbox_wrapper[0];
|
||||||
var searchbox = searchbox_wrapper.getElementsByTagName("input")[1];
|
var searchbox = searchbox_wrapper.getElementsByTagName("input")[1];
|
||||||
|
|
||||||
|
/*
|
||||||
|
Textarea shortcuts
|
||||||
|
*/
|
||||||
document.addEventListener("keydown", function(key){
|
document.addEventListener("keydown", function(key){
|
||||||
|
|
||||||
switch(key.keyCode){
|
switch(key.keyCode){
|
||||||
|
@ -695,4 +696,261 @@ if(searchbox_wrapper.length !== 0){
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
Autocompleter
|
||||||
|
*/
|
||||||
|
if( // make sure the user wants it
|
||||||
|
document.cookie.includes("scraper_ac=") &&
|
||||||
|
document.cookie.includes("scraper_ac=disabled") === false
|
||||||
|
){
|
||||||
|
|
||||||
|
var autocomplete_cache = [];
|
||||||
|
var focuspos = -1;
|
||||||
|
var list = [];
|
||||||
|
var autocomplete_div = document.getElementsByClassName("autocomplete")[0];
|
||||||
|
|
||||||
|
if(
|
||||||
|
document.cookie.includes("scraper_ac=auto") &&
|
||||||
|
typeof scraper_dropdown != "undefined"
|
||||||
|
){
|
||||||
|
|
||||||
|
var ac_req_appendix = "&scraper=" + scraper_dropdown.value;
|
||||||
|
}else{
|
||||||
|
|
||||||
|
var ac_req_appendix = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function getsearchboxtext(){
|
||||||
|
|
||||||
|
var value =
|
||||||
|
searchbox.value
|
||||||
|
.trim()
|
||||||
|
.replace(
|
||||||
|
/ +/g,
|
||||||
|
" "
|
||||||
|
)
|
||||||
|
.toLowerCase();
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
searchbox.addEventListener("input", async function(){
|
||||||
|
|
||||||
|
// ratelimit on input only
|
||||||
|
// dont ratelimit if we already have res
|
||||||
|
if(typeof autocomplete_cache[getsearchboxtext()] != "undefined"){
|
||||||
|
|
||||||
|
await getac();
|
||||||
|
}else{
|
||||||
|
|
||||||
|
await getac_ratelimit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function getac(){
|
||||||
|
|
||||||
|
var curvalue = getsearchboxtext();
|
||||||
|
|
||||||
|
if(curvalue == ""){
|
||||||
|
|
||||||
|
// hide autocompleter
|
||||||
|
autocomplete_div.style.display = "none";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(typeof autocomplete_cache[curvalue] == "undefined"){
|
||||||
|
|
||||||
|
/*
|
||||||
|
Fetch autocomplete
|
||||||
|
*/
|
||||||
|
// make sure we dont fetch same thing twice
|
||||||
|
autocomplete_cache[curvalue] = [];
|
||||||
|
|
||||||
|
var res = await fetch("/api/v1/ac?s=" + encodeURIComponent(curvalue) + ac_req_appendix);
|
||||||
|
var json = await res.json();
|
||||||
|
|
||||||
|
autocomplete_cache[curvalue] = json[1];
|
||||||
|
|
||||||
|
if(curvalue == getsearchboxtext()){
|
||||||
|
|
||||||
|
render_ac(curvalue, autocomplete_cache[curvalue]);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
render_ac(curvalue, autocomplete_cache[curvalue]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var ac_func = null;
|
||||||
|
function getac_ratelimit(){
|
||||||
|
|
||||||
|
return new Promise(async function(resolve, reject){
|
||||||
|
|
||||||
|
if(ac_func !== null){
|
||||||
|
|
||||||
|
clearTimeout(ac_func);
|
||||||
|
}//else{
|
||||||
|
|
||||||
|
// no ratelimits
|
||||||
|
//getac();
|
||||||
|
//}
|
||||||
|
|
||||||
|
ac_func =
|
||||||
|
setTimeout(function(){
|
||||||
|
|
||||||
|
ac_func = null;
|
||||||
|
getac(); // get results after 100ms of no keystroke
|
||||||
|
resolve();
|
||||||
|
}, 300);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function render_ac(query, list){
|
||||||
|
|
||||||
|
if(list.length === 0){
|
||||||
|
|
||||||
|
autocomplete_div.style.display = "none";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
html = "";
|
||||||
|
|
||||||
|
// prepare regex
|
||||||
|
var highlight = query.split(" ");
|
||||||
|
var regex = [];
|
||||||
|
|
||||||
|
for(var k=0; k<highlight.length; k++){
|
||||||
|
|
||||||
|
// espace regex
|
||||||
|
regex.push(
|
||||||
|
highlight[k].replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
regex = new RegExp(highlight.join("|"), "gi");
|
||||||
|
|
||||||
|
for(var i=0; i<list.length; i++){
|
||||||
|
|
||||||
|
html +=
|
||||||
|
'<div tabindex="0" class="entry" onclick="handle_entry_click(this);">' +
|
||||||
|
htmlspecialchars(
|
||||||
|
list[i]
|
||||||
|
).replace(
|
||||||
|
regex,
|
||||||
|
'<u>$&</u>'
|
||||||
|
) +
|
||||||
|
'</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
autocomplete_div.innerHTML = html;
|
||||||
|
autocomplete_div.style.display = "block";
|
||||||
|
}
|
||||||
|
|
||||||
|
var should_focus = false;
|
||||||
|
document.addEventListener("keydown", function(event){
|
||||||
|
|
||||||
|
if(event.key == "Escape"){
|
||||||
|
|
||||||
|
document.activeElement.blur();
|
||||||
|
focuspos = -1;
|
||||||
|
autocomplete_div.style.display = "none";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(
|
||||||
|
is_click_within(event.target, "searchbox") === false ||
|
||||||
|
typeof autocomplete_cache[getsearchboxtext()] == "undefined"
|
||||||
|
){
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(event.key){
|
||||||
|
|
||||||
|
case "ArrowUp":
|
||||||
|
event.preventDefault();
|
||||||
|
focuspos--;
|
||||||
|
if(focuspos === -2){
|
||||||
|
|
||||||
|
focuspos = autocomplete_cache[getsearchboxtext()].length - 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "ArrowDown":
|
||||||
|
case "Tab":
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
focuspos++;
|
||||||
|
if(focuspos >= autocomplete_cache[getsearchboxtext()].length){
|
||||||
|
|
||||||
|
focuspos = -1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "Enter":
|
||||||
|
should_focus = true;
|
||||||
|
|
||||||
|
if(focuspos !== -1){
|
||||||
|
|
||||||
|
// replace input content
|
||||||
|
event.preventDefault();
|
||||||
|
searchbox.value =
|
||||||
|
autocomplete_div.getElementsByClassName("entry")[focuspos].innerText;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
focuspos = -1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(focuspos === -1){
|
||||||
|
|
||||||
|
searchbox.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
autocomplete_div.getElementsByClassName("entry")[focuspos].focus();
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener("blur", function(){
|
||||||
|
|
||||||
|
autocomplete_div.style.display = "none";
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("keyup", function(event){
|
||||||
|
|
||||||
|
// handle ENTER key on entry
|
||||||
|
if(should_focus){
|
||||||
|
|
||||||
|
should_focus = false;
|
||||||
|
searchbox.focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("mousedown", function(event){
|
||||||
|
|
||||||
|
// hide input if click is outside
|
||||||
|
if(is_click_within(event.target, "searchbox") === false){
|
||||||
|
|
||||||
|
autocomplete_div.style.display = "none";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function handle_entry_click(event){
|
||||||
|
|
||||||
|
searchbox.value = event.innerText;
|
||||||
|
focuspos = -1;
|
||||||
|
searchbox.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
searchbox.addEventListener("focus", function(){
|
||||||
|
|
||||||
|
focuspos = -1;
|
||||||
|
getac();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,31 +149,27 @@ h3,h4,h5,h6{
|
||||||
left:-1px;
|
left:-1px;
|
||||||
right:-1px;
|
right:-1px;
|
||||||
background:var(--282828);
|
background:var(--282828);
|
||||||
border:1px solid var(--504945);
|
border:1px solid var(--928374);
|
||||||
border-top:none;
|
border-top:none;
|
||||||
border-radius:0 0 2px 2px;
|
border-radius:0 0 2px 2px;
|
||||||
z-index:10;
|
z-index:10;
|
||||||
|
overflow:hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.autocomplete .entry{
|
.autocomplete .entry{
|
||||||
overflow:hidden;
|
overflow:hidden;
|
||||||
padding:4px 10px;
|
padding:4px 10px;
|
||||||
cursor:pointer;
|
cursor:pointer;
|
||||||
|
outline:none;
|
||||||
|
user-select:none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.autocomplete .entry:hover{
|
.autocomplete .entry:hover{
|
||||||
background:var(--3c3836);
|
background:var(--3c3836);
|
||||||
}
|
}
|
||||||
|
|
||||||
.autocomplete .title{
|
.autocomplete .entry:focus{
|
||||||
float:left;
|
background:var(--3c3836);
|
||||||
}
|
|
||||||
|
|
||||||
.autocomplete .subtext{
|
|
||||||
float:right;
|
|
||||||
font-size:14px;
|
|
||||||
color:var(--928374);
|
|
||||||
margin-left:7px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tabs */
|
/* Tabs */
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<head>
|
<head>
|
||||||
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
|
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
|
||||||
<title>{%title%}</title>
|
<title>{%title%}</title>
|
||||||
<link rel="stylesheet" href="/static/style.css?v3">
|
<link rel="stylesheet" href="/static/style.css?v4">
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
<meta name="robots" content="{%index%}index,{%index%}follow">
|
<meta name="robots" content="{%index%}index,{%index%}follow">
|
||||||
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
|
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
|
||||||
<title>4get</title>
|
<title>4get</title>
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
<link rel="stylesheet" href="/static/style.css?v3">
|
<link rel="stylesheet" href="/static/style.css?v4">
|
||||||
<meta name="robots" content="index,follow">
|
<meta name="robots" content="index,follow">
|
||||||
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
||||||
<meta name="description" content="4get.ca: They live in our walls!">
|
<meta name="description" content="4get.ca: They live in our walls!">
|
||||||
|
@ -33,6 +33,6 @@
|
||||||
Report a problem: <a href="https://lolcat.ca">lolcat.ca</a>
|
Report a problem: <a href="https://lolcat.ca">lolcat.ca</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="/static/client.js?v3"></script>
|
<script src="/static/client.js?v4"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
Loading…
Reference in a new issue