mirror of
https://codeberg.org/vlw/victorwesterlund.com.git
synced 2025-09-14 11:33:41 +02:00
dev21w34g
This commit is contained in:
parent
f2f1a3b013
commit
a48d645f12
6 changed files with 32 additions and 13 deletions
|
@ -6,18 +6,21 @@ export default class Search {
|
||||||
|
|
||||||
this.lastQuery = "";
|
this.lastQuery = "";
|
||||||
this.throttle = null;
|
this.throttle = null;
|
||||||
|
this.controller = null; // AbortController will be assigned here
|
||||||
|
|
||||||
this.results = results;
|
this.results = results;
|
||||||
this.input = input;
|
this.input = input;
|
||||||
this.input?.addEventListener("keyup",event => this.keyEvent(event)) ?? false;
|
this.input?.addEventListener("keyup",event => this.keyEvent(event)) ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Destroy the result DOM tree
|
||||||
clearResults() {
|
clearResults() {
|
||||||
while(this.results.firstChild) {
|
while(this.results.firstChild) {
|
||||||
this.results.removeChild(this.results.lastChild);
|
this.results.removeChild(this.results.lastChild);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Display output as HTML
|
||||||
output(html) {
|
output(html) {
|
||||||
this.clearResults();
|
this.clearResults();
|
||||||
if(typeof html === "string") {
|
if(typeof html === "string") {
|
||||||
|
@ -27,6 +30,7 @@ export default class Search {
|
||||||
this.results.appendChild(html);
|
this.results.appendChild(html);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Display a status message in a paragraph
|
||||||
status(text,classList = false) {
|
status(text,classList = false) {
|
||||||
const element = document.createElement("p");
|
const element = document.createElement("p");
|
||||||
if(classList !== false) {
|
if(classList !== false) {
|
||||||
|
@ -37,12 +41,15 @@ export default class Search {
|
||||||
this.output(element);
|
this.output(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fetch search results from endpoint
|
||||||
async search(query) {
|
async search(query) {
|
||||||
const url = new URL(this.endpoint);
|
const url = new URL(this.endpoint);
|
||||||
url.searchParams.set("q",query);
|
url.searchParams.set("q",query);
|
||||||
|
|
||||||
const timeout = new Promise(reject => setTimeout(() => reject("Request timed out"),3000));
|
const timeout = new Promise(reject => setTimeout(() => reject("Request timed out"),3000));
|
||||||
|
// Fetch response from server
|
||||||
const api = fetch(url,{
|
const api = fetch(url,{
|
||||||
|
signal: this.controller.signal,
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "text/html"
|
"Content-Type": "text/html"
|
||||||
}
|
}
|
||||||
|
@ -51,29 +58,33 @@ export default class Search {
|
||||||
const result = Promise.race([api,timeout]);
|
const result = Promise.race([api,timeout]);
|
||||||
result.then(response => {
|
result.then(response => {
|
||||||
if(!response.ok) {
|
if(!response.ok) {
|
||||||
console.error("Response from server:",response);
|
this.status("oh no, something went wrong","error");
|
||||||
throw new Error("Invalid response from server");
|
throw new Error("Invalid response from server");
|
||||||
}
|
}
|
||||||
return response.text();
|
return response.text();
|
||||||
})
|
})
|
||||||
.then(html => this.output(html))
|
.then(html => this.output(html))
|
||||||
.catch(error => this.status(error,"error"));
|
.catch(error => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait until the user stops typing for a few miliseconds
|
// Wait until the user stops typing for a few miliseconds
|
||||||
queue(query) {
|
queue(query) {
|
||||||
clearTimeout(this.throttle);
|
clearTimeout(this.throttle);
|
||||||
|
this.controller = new AbortController(); // Spawn a new AbortController for each fetch
|
||||||
this.throttle = setTimeout(() => this.search(query),500);
|
this.throttle = setTimeout(() => this.search(query),500);
|
||||||
}
|
}
|
||||||
|
|
||||||
keyEvent(event) {
|
keyEvent(event) {
|
||||||
const query = event.target.value;
|
const query = event.target.value;
|
||||||
|
// Don't do the search thing if query is too weak
|
||||||
if(query.length < 1) {
|
if(query.length < 1) {
|
||||||
|
this.controller.abort(); // Abort queued search
|
||||||
this.lastQuery = "";
|
this.lastQuery = "";
|
||||||
this.status("search results will appear here as you type");
|
this.status("search results will appear here as you type");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pressing a modifier key (Ctrl, Shift etc.) doesn't change the query
|
||||||
if(query === this.lastQuery) {
|
if(query === this.lastQuery) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
6
public/assets/js/noscript.js
Normal file
6
public/assets/js/noscript.js
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
const search = document.getElementById("search").children[0];
|
||||||
|
const results = document.getElementById("results").children[0];
|
||||||
|
|
||||||
|
search.style.setProperty("display","none");
|
||||||
|
results.classList.add("error");
|
||||||
|
results.innerText = "Sorry, your browser isn't supported yet";
|
|
@ -1,7 +0,0 @@
|
||||||
import { default as Search } from "./modules/Search.mjs";
|
|
||||||
|
|
||||||
const searchBox = document.getElementById("search")?.children[0] ?? false;
|
|
||||||
const resultsContainer = document.getElementById("results");
|
|
||||||
|
|
||||||
new Search(searchBox,resultsContainer);
|
|
||||||
window.addEventListener("keydown",() => searchBox.focus());
|
|
9
public/assets/js/search.mjs
Normal file
9
public/assets/js/search.mjs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import { default as Search } from "./modules/Search.mjs";
|
||||||
|
|
||||||
|
const searchBox = document.getElementById("search")?.getElementsByTagName("input")[0] ?? false;
|
||||||
|
const resultsContainer = document.getElementById("results");
|
||||||
|
|
||||||
|
new Search(searchBox,resultsContainer);
|
||||||
|
|
||||||
|
// Set focus on searchbox when typing from anywhere
|
||||||
|
window.addEventListener("keydown",() => searchBox.focus());
|
|
@ -9,7 +9,6 @@
|
||||||
<link rel="icon" href="assets/img/favicon.png">
|
<link rel="icon" href="assets/img/favicon.png">
|
||||||
<link rel="stylesheet" href="assets/css/style.css">
|
<link rel="stylesheet" href="assets/css/style.css">
|
||||||
<link rel="stylesheet" href="assets/css/search.css">
|
<link rel="stylesheet" href="assets/css/search.css">
|
||||||
<link rel="preload" as="font" href="assets/fonts/RobotoMono-Bold.woff2">
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
|
@ -21,6 +20,7 @@
|
||||||
<div id="results">
|
<div id="results">
|
||||||
<p>search results will appear here as you type</p>
|
<p>search results will appear here as you type</p>
|
||||||
</div>
|
</div>
|
||||||
<script type="module" src="assets/js/search.js"></script>
|
<script type="module" src="assets/js/search.mjs"></script>
|
||||||
|
<script nomodule defer src="assets/js/noscript.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
|
|
||||||
// Perform a seach on the given query and return the results as an array
|
// Perform a seach on the given query and return the results as an array
|
||||||
private function get_results() {
|
private function get_results() {
|
||||||
$sql = "SELECT template,title,content,href FROM `search` WHERE `title` LIKE '%{$this->query}%'";
|
$sql = "SELECT template,title,content,href FROM `search` WHERE `title` LIKE '%{$this->query}%' OR `content` LIKE '%{$this->query}%'";
|
||||||
$rows = $this->get_rows($sql);
|
$rows = $this->get_rows($sql);
|
||||||
return $rows;
|
return $rows;
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,7 @@
|
||||||
$results = $this->get_results();
|
$results = $this->get_results();
|
||||||
|
|
||||||
if(count($results) < 1) {
|
if(count($results) < 1) {
|
||||||
$results[] = ["message","info","no results :("];
|
$results[] = ["message","info","no results 😞"];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load HTML and format each response from template
|
// Load HTML and format each response from template
|
||||||
|
|
Loading…
Add table
Reference in a new issue