diff --git a/.env.example.ini b/.env.example.ini
index a214a34..52c9b5a 100755
--- a/.env.example.ini
+++ b/.env.example.ini
@@ -1,20 +1,15 @@
-[client_api]
-base_url = ""
-api_key = ""
-verify_peer = true
-
-[client_time_available]
-time_zone = "Europe/Stockholm"
-available_to_hour = 0;
-reply_average_hours = 0;
-available_from_hour = 0;
-
-[server_database]
+[mariadb]
host = ""
user = ""
pass = ""
db = ""
-[server_forgejo]
+[config_time_available]
+time_zone = "Europe/Stockholm"
+available_to_hour = 0;
+reply_average_hours = 0;
+available_from_hour = 0;
+
+[service_forgejo]
base_url = ""
scan_profiles = ""
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 809e797..04b1c31 100755
--- a/.gitignore
+++ b/.gitignore
@@ -1,22 +1,2 @@
-# Public assets #
-#################
-public/.well-known
-public/assets/js/modules/npm
-
-# Bootstrapping #
-#################
vendor
-node_modules
-.env.ini
-
-# OS generated files #
-######################
-.DS_Store
-.DS_Store?
-._*
-.Spotlight-V100
-.Trashes
-Icon?
-ehthumbs.db
-Thumbs.db
-.directory
+.env.ini
\ No newline at end of file
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..1242f19
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,6 @@
+[submodule "reflect"]
+ path = reflect
+ url = https://codeberg.org/reflect/reflect
+[submodule "vegvisir"]
+ path = vegvisir
+ url = https://codeberg.org/vegvisir/vegvisir
diff --git a/api/update/GET.php b/api/update/GET.php
new file mode 100644
index 0000000..7cba5ae
--- /dev/null
+++ b/api/update/GET.php
@@ -0,0 +1,45 @@
+GET([
+ new Rules(self::KEY_SERVICE)
+ ->type(Type::ENUM, ServiceEnum::values())
+ ->default(ServiceEnum::ALL->value)
+ ]));
+ }
+
+ public function update_timeline(): bool {
+ return new GenerateTimeline()->generate();
+ }
+
+ public function main(): Response {
+ switch ($_GET[self::KEY_SERVICE]) {
+ case ServiceEnum::TIMELINE->value:
+ return new Response("OK");
+
+ case ServiceEnum::ALL->value:
+ default:
+ return new Response($this->update_timeline());
+ }
+ }
+ }
\ No newline at end of file
diff --git a/api/work/GET.php b/api/work/GET.php
new file mode 100644
index 0000000..b001eea
--- /dev/null
+++ b/api/work/GET.php
@@ -0,0 +1,28 @@
+ array_map(fn(Tag $tag): string => $tag->label->name, Tag::from($work)),
+ "actions" => [],
+ "details" => $work
+ ];
+ }
+
+ public function __construct() {
+ parent::__construct();
+ }
+
+ public function main(): Response {
+ return new Response(array_map(fn(Work $work): object => self::entity($work), Work::all()));
+ }
+ }
\ No newline at end of file
diff --git a/composer.json b/composer.json
index 07a4b64..9ca644e 100755
--- a/composer.json
+++ b/composer.json
@@ -1,6 +1,5 @@
{
"require": {
- "reflect/client": "dev-master",
"vlw/mysql": "dev-master",
"vlw/xenum": "dev-master"
},
diff --git a/composer.lock b/composer.lock
index a4dd9a1..e07d7a5 100755
--- a/composer.lock
+++ b/composer.lock
@@ -4,43 +4,15 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "f74f68a452514a9d4dd011cef7648a7f",
+ "content-hash": "a7ce20d192550ef2d037220b593b5eb9",
"packages": [
- {
- "name": "reflect/client",
- "version": "dev-master",
- "source": {
- "type": "git",
- "url": "https://codeberg.org/reflect/client-php",
- "reference": "89a8c041044c8c60cefafc4716d5d61b96c43e06"
- },
- "default-branch": true,
- "type": "library",
- "autoload": {
- "psr-4": {
- "Reflect\\": "src/Reflect/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "GPL-2.0-only"
- ],
- "authors": [
- {
- "name": "Victor Westerlund",
- "email": "victor.vesterlund@gmail.com"
- }
- ],
- "description": "Extendable PHP interface for communicating with Reflect API over HTTP or UNIX sockets",
- "time": "2024-04-06T14:55:04+00:00"
- },
{
"name": "vlw/mysql",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://codeberg.org/vlw/php-mysql",
- "reference": "c64eb96049907da60dc9f237d26aef0e531b0015"
+ "reference": "0e367f797fa9348408881ed758976f21e8c667e4"
},
"default-branch": true,
"type": "library",
@@ -60,7 +32,7 @@
}
],
"description": "Abstraction library for common MySQL/MariaDB DML operations with php-mysqli",
- "time": "2025-01-30T09:33:10+00:00"
+ "time": "2025-07-29T07:46:46+00:00"
},
{
"name": "vlw/xenum",
@@ -68,7 +40,7 @@
"source": {
"type": "git",
"url": "https://codeberg.org/vlw/php-xenum",
- "reference": "1c997a5574656b88a62f5ee160ee5a6439932a2f"
+ "reference": "ba3f43a9e2787bf938cfbfcb85ea87e5062df294"
},
"default-branch": true,
"type": "library",
@@ -88,14 +60,13 @@
}
],
"description": "PHP eXtended Enums. The missing quality-of-life features from PHP 8+ Enums",
- "time": "2024-12-02T10:36:32+00:00"
+ "time": "2025-05-10T11:28:03+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "dev",
"stability-flags": {
- "reflect/client": 20,
"vlw/mysql": 20,
"vlw/xenum": 20
},
diff --git a/endpoints/about/languages/DELETE.php b/endpoints/about/languages/DELETE.php
deleted file mode 100644
index 4da5b58..0000000
--- a/endpoints/about/languages/DELETE.php
+++ /dev/null
@@ -1,40 +0,0 @@
-ruleset = new Ruleset(strict: true);
- $this->ruleset->validate_or_exit();
-
- parent::__construct();
- }
-
- private function languages(): array {
- $resp = (new Call(Endpoints::ABOUT_LANGUAGES->value))->get();
-
- return array_column($resp->output(), LanguagesTable::ID->value);
- }
-
- // Delete languages cache file if it exists
- public function main(): Response {
- $this->db->for(LanguagesTable::NAME);
-
- foreach ($this->languages() as $language){
- $this->db->delete([LanguagesTable::ID->value => $language]);
- }
-
- return new Response();
- }
- }
\ No newline at end of file
diff --git a/endpoints/about/languages/GET.php b/endpoints/about/languages/GET.php
deleted file mode 100644
index b4b4ba0..0000000
--- a/endpoints/about/languages/GET.php
+++ /dev/null
@@ -1,43 +0,0 @@
-ruleset = new Ruleset(strict: true);
-
- $this->ruleset->GET([
- (new Rules(LanguagesTable::ID->value))
- ->type(Type::STRING)
- ->min(1)
- ->max(parent::SIZE_VARCHAR),
-
- (new Rules(LanguagesTable::BYTES->value))
- ->type(Type::NUMBER)
- ->min(1)
- ->max(parent::SIZE_UINT32)
- ]);
-
- $this->ruleset->validate_or_exit();
-
- parent::__construct();
- }
-
- public function main(): Response {
- return $this->list(LanguagesTable::NAME, LanguagesTable::values(), [
- LanguagesTable::BYTES->value => Order::DESC
- ]);
- }
- }
\ No newline at end of file
diff --git a/endpoints/coffee/GET.php b/endpoints/coffee/GET.php
deleted file mode 100644
index 151b6dc..0000000
--- a/endpoints/coffee/GET.php
+++ /dev/null
@@ -1,38 +0,0 @@
-ruleset = new Ruleset(strict: true);
-
- $this->ruleset->GET([
- (new Rules(CoffeeTable::ID->value))
- ->type(Type::NUMBER)
- ->min(1)
- ->max(parent::SIZE_UINT32)
- ]);
-
- $this->ruleset->validate_or_exit();
-
- parent::__construct();
- }
-
- public function main(): Response {
- return $this->list(CoffeeTable::NAME, CoffeeTable::values(), [
- CoffeeTable::ID->value => Order::DESC
- ]);
- }
- }
\ No newline at end of file
diff --git a/endpoints/coffee/POST.php b/endpoints/coffee/POST.php
deleted file mode 100644
index 7c96ad9..0000000
--- a/endpoints/coffee/POST.php
+++ /dev/null
@@ -1,38 +0,0 @@
-ruleset = new Ruleset(strict: true);
-
- $this->ruleset->POST([
- (new Rules(CoffeeTable::ID->value))
- ->type(Type::NUMBER)
- ->min(1)
- ->max(parent::SIZE_UINT32)
- ->default(time()),
- ]);
-
- $this->ruleset->validate_or_exit();
-
- parent::__construct();
- }
-
- public function main(): Response {
- return $this->db->for(CoffeeTable::NAME)->insert($_POST) === true
- ? new Response(null, 201)
- : new Response("Database error", 500);
- }
- }
\ No newline at end of file
diff --git a/endpoints/coffee/stats/GET.php b/endpoints/coffee/stats/GET.php
deleted file mode 100644
index b3990ba..0000000
--- a/endpoints/coffee/stats/GET.php
+++ /dev/null
@@ -1,28 +0,0 @@
-ruleset = new Ruleset(strict: true);
- $this->ruleset->validate_or_exit();
-
- parent::__construct();
- }
-
- public function main(): Response {
- return $this->list(StatsTable::NAME, StatsTable::values());
- }
- }
\ No newline at end of file
diff --git a/endpoints/coffee/stats/POST.php b/endpoints/coffee/stats/POST.php
deleted file mode 100644
index 6d05917..0000000
--- a/endpoints/coffee/stats/POST.php
+++ /dev/null
@@ -1,35 +0,0 @@
-ruleset = new Ruleset(strict: true);
- $this->ruleset->validate_or_exit();
-
- parent::__construct();
- }
-
- public function main(): Response {
- $truncate = $this->db->execute_query("DELETE FROM `" . StatsTable::NAME . "`");
-
- // Add a dummy row to run the MariaDB INSERT AFTER Trigger on the coffee database table
- $insert = $this->db->for(CoffeeTable::NAME)->insert([CoffeeTable::ID->value => 0]);
- // Remove the dummy row
- $remove = $this->db->for(CoffeeTable::NAME)->where([CoffeeTable::ID->value => 0])->delete();
-
- return $truncate && $insert && $remove ? new Response() : new Response("Error", 500);
- }
- }
\ No newline at end of file
diff --git a/endpoints/messages/POST.php b/endpoints/messages/POST.php
deleted file mode 100644
index e6cbdd7..0000000
--- a/endpoints/messages/POST.php
+++ /dev/null
@@ -1,43 +0,0 @@
-ruleset = new Ruleset(strict: true);
-
- $this->ruleset->POST([
- (new Rules(MessagesTable::EMAIL->value))
- ->type(Type::STRING)
- ->max(255)
- ->default(null),
-
- (new Rules(MessagesTable::MESSAGE->value))
- ->required()
- ->type(Type::STRING)
- ->min(1)
- ->max(parent::SIZE_TEXT)
- ]);
-
- $this->ruleset->validate_or_exit();
-
- parent::__construct();
- }
-
- public function main(): Response {
- $_POST[MessagesTable::TIMESTAMP_CREATED->value] = time();
-
- return $this->db->for(MessagesTable::NAME)->insert($_POST) === true
- ? new Response(null, 201)
- : new Response("Failed to send message", 500);
- }
- }
\ No newline at end of file
diff --git a/endpoints/search/DELETE.php b/endpoints/search/DELETE.php
deleted file mode 100644
index 6d9e445..0000000
--- a/endpoints/search/DELETE.php
+++ /dev/null
@@ -1,35 +0,0 @@
-ruleset = new Ruleset(strict: true);
-
- $this->ruleset->POST([
- (new Rules(SearchTable::ID->value))
- ->required()
- ->type(Type::STRING)
- ->min(2)
- ->max(parent::SIZE_VARCHAR)
- ]);
-
- $this->ruleset->validate_or_exit();
-
- parent::__construct();
- }
-
- public function main(): Response {
- return $this->db->for(SearchTable::NAME)->delete($_POST) === true ? new Response() : new Response("", 500);
- }
- }
\ No newline at end of file
diff --git a/endpoints/search/GET.php b/endpoints/search/GET.php
deleted file mode 100644
index 46c793a..0000000
--- a/endpoints/search/GET.php
+++ /dev/null
@@ -1,85 +0,0 @@
-ruleset = new Ruleset(strict: true);
-
- $this->ruleset->GET([
- (new Rules(SearchTable::QUERY->value))
- ->type(Type::STRING)
- ->min(2)
- ->max(parent::SIZE_VARCHAR)
- ->default(null),
-
- (new Rules(SearchTable::ID->value))
- ->type(Type::STRING)
- ->min(1)
- ->max(10)
- ->default(null),
-
- (new Rules(SearchTable::CATEGORY->value))
- ->type(Type::ENUM, SearchCategoryEnum::names())
- ->default(null)
- ]);
-
- $this->ruleset->validate_or_exit();
-
- parent::__construct();
- }
-
- private static function get_query(): string {
- preg_match_all("/[a-zA-Z0-9]+/", $_GET[SearchTable::QUERY->value], $matches);
-
- return strtolower(implode("", $matches[0]));
- }
-
- public function main(): Response {
- $result = $this->db->for(SearchTable::NAME);
-
- if ($_GET[SearchTable::ID->value]) {
- $result = $result->where([SearchTable::ID->value => $_GET[SearchTable::ID->value]]);
- } else if ($_GET[SearchTable::QUERY->value]) {
- $query = self::get_query();
-
- $filter = [
- SearchTable::QUERY->value => [
- Operators::LIKE->value => "%{$query}%"
- ]
- ];
-
- if ($_GET[SearchTable::CATEGORY->value]) {
- $filter[SearchTable::CATEGORY->value] = $_GET[SearchTable::CATEGORY->value];
- }
-
- $result = $result->where($filter);
- } else {
- new Response([], 400);
- }
-
- $result = $result->select([
- SearchTable::ID->value,
- SearchTable::TITLE->value,
- SearchTable::SUMMARY->value,
- SearchTable::CATEGORY->value,
- SearchTable::HREF->value
- ]);
-
- return $result->num_rows > 0
- ? new Response($result->fetch_all(MYSQLI_ASSOC))
- : new Response([], 404);
- }
- }
\ No newline at end of file
diff --git a/endpoints/search/POST.php b/endpoints/search/POST.php
deleted file mode 100644
index 95548e8..0000000
--- a/endpoints/search/POST.php
+++ /dev/null
@@ -1,67 +0,0 @@
-ruleset = new Ruleset(strict: true);
- $this->ruleset->validate_or_exit();
-
- parent::__construct();
- }
-
- private static function truncate_string(string $input): string {
- return substr($input, 0, SEARCH_QUERY_MAX_LENGTH);
- }
-
- private static function create_query(string $input): string {
- preg_match_all("/[a-zA-Z0-9]+/", $input, $matches);
-
- return self::truncate_string(strtolower(implode("", $matches[0])));
- }
-
- private function index_work(): void {
- foreach ((new Call(Endpoints::WORK->value))->get()->output() as $result) {
- $query = self::create_query(implode("", array_values($result)), 0, SEARCH_QUERY_MAX_LENGTH);
-
- // Get actions related to current result
- $actions = (new Call(Endpoints::WORK_ACTIONS->value))->params([
- ActionsTable::REF_WORK_ID->value => $result[WorkTable::ID->value]
- ])->get()->output();
-
- $this->db->for(SearchTable::NAME)->insert([
- SearchTable::QUERY->value => $query,
- SearchTable::ID->value => crc32($query),
- SearchTable::TITLE->value => self::truncate_string($result[WorkTable::TITLE->value]),
- SearchTable::SUMMARY->value => self::truncate_string($result[WorkTable::SUMMARY->value]),
- SearchTable::CATEGORY->value => SearchCategoryEnum::WORK->name,
- // Use first action as link for search result
- SearchTable::HREF->value => $actions
- ? self::truncate_string($actions[0][ActionsTable::HREF->value])
- : null
- ]);
- }
- }
-
- public function main(): Response {
- $this->index_work();
- return new Response();
- }
- }
\ No newline at end of file
diff --git a/endpoints/update/GET.php b/endpoints/update/GET.php
deleted file mode 100644
index 6044689..0000000
--- a/endpoints/update/GET.php
+++ /dev/null
@@ -1,26 +0,0 @@
-ruleset = new Ruleset(strict: true);
- $this->ruleset->validate_or_exit();
- }
-
- // Update all runtime database endpoints
- public function main(): Response {
- (new Call(Endpoints::SEARCH->value))->post();
- (new Call(Endpoints::COFFEE_STATS->value))->post();
- (new Call(Endpoints::ABOUT_LANGUAGES->value))->post();
-
- return new Response();
- }
- }
\ No newline at end of file
diff --git a/endpoints/work/GET.php b/endpoints/work/GET.php
deleted file mode 100644
index 01ecdc4..0000000
--- a/endpoints/work/GET.php
+++ /dev/null
@@ -1,49 +0,0 @@
-ruleset = new Ruleset(strict: true);
-
- $this->ruleset->GET([
- (new Rules(WorkTable::ID->value))
- ->type(Type::STRING)
- ->min(1)
- ->max(parent::SIZE_VARCHAR),
-
- (new Rules(WorkTable::TITLE->value))
- ->type(Type::STRING)
- ->max(parent::SIZE_VARCHAR),
-
- (new Rules(WorkTable::SUMMARY->value))
- ->type(Type::STRING)
- ->max(parent::SIZE_TEXT),
-
- (new Rules(WorkTable::CREATED->value))
- ->type(Type::STRING)
- ->min(1)
- ->max(parent::SIZE_VARCHAR)
- ]);
-
- $this->ruleset->validate_or_exit();
-
- parent::__construct();
- }
-
- public function main(): Response {
- return $this->list(WorkTable::NAME, WorkTable::values(), [
- WorkTable::CREATED->value => Order::DESC
- ]);
- }
- }
\ No newline at end of file
diff --git a/endpoints/work/actions/GET.php b/endpoints/work/actions/GET.php
deleted file mode 100644
index f92e0bb..0000000
--- a/endpoints/work/actions/GET.php
+++ /dev/null
@@ -1,36 +0,0 @@
-ruleset = new Ruleset(strict: true);
-
- $this->ruleset->GET([
- (new Rules(ActionsTable::REF_WORK_ID->value))
- ->type(Type::STRING)
- ->min(1)
- ->max(parent::SIZE_VARCHAR)
- ]);
-
- $this->ruleset->validate_or_exit();
-
- parent::__construct();
- }
-
- public function main(): Response {
- return $this->list(ActionsTable::NAME, ActionsTable::values(), [
- ActionsTable::ORDER_IDX->value => Order::DESC
- ]);
- }
- }
\ No newline at end of file
diff --git a/endpoints/work/tags/GET.php b/endpoints/work/tags/GET.php
deleted file mode 100644
index a8a9f4c..0000000
--- a/endpoints/work/tags/GET.php
+++ /dev/null
@@ -1,35 +0,0 @@
-ruleset = new Ruleset(strict: true);
-
- $this->ruleset->GET([
- (new Rules(TagsTable::REF_WORK_ID->value))
- ->min(1)
- ->max(parent::SIZE_VARCHAR),
-
- (new Rules(TagsTable::LABEL->value))
- ->type(Type::ENUM, TagsLabelEnum::names())
- ]);
-
- $this->ruleset->validate_or_exit();
-
- parent::__construct();
- }
-
- public function main(): Response {
- return $this->list(TagsTable::NAME, TagsTable::values());
- }
- }
\ No newline at end of file
diff --git a/endpoints/work/timeline/GET.php b/endpoints/work/timeline/GET.php
deleted file mode 100644
index b9b442d..0000000
--- a/endpoints/work/timeline/GET.php
+++ /dev/null
@@ -1,53 +0,0 @@
-ruleset = new Ruleset(strict: true);
-
- $this->ruleset->GET([
- (new Rules(TimelineTable::REF_WORK_ID->value))
- ->type(Type::STRING)
- ->min(1)
- ->max(parent::SIZE_VARCHAR),
-
- (new Rules(TimelineTable::YEAR->value))
- ->type(Type::NUMBER)
- ->min(0)
- ->max(parent::SIZE_UINT16),
-
- (new Rules(TimelineTable::MONTH->value))
- ->type(Type::NUMBER)
- ->min(0)
- ->max(parent::SIZE_UINT8),
-
- (new Rules(TimelineTable::DAY->value))
- ->type(Type::NUMBER)
- ->min(0)
- ->max(parent::SIZE_UINT8)
- ]);
-
- $this->ruleset->validate_or_exit();
-
- parent::__construct();
- }
-
- public function main(): Response {
- return $this->list(TimelineTable::NAME, TimelineTable::values(), [
- TimelineTable::YEAR->value => Order::DESC,
- TimelineTable::MONTH->value => Order::DESC,
- TimelineTable::DAY->value => Order::DESC
- ]);
- }
- }
\ No newline at end of file
diff --git a/install.sh b/install.sh
deleted file mode 100644
index 7879587..0000000
--- a/install.sh
+++ /dev/null
@@ -1,10 +0,0 @@
-# Install dependencies
-composer install --optimize-autoloader
-npm install
-
-# (Re)create public NPM modules folder
-rm -r public/assets/js/modules/npm
-mkdir public/assets/js/modules/npm
-
-# Create link to Elevent MJS from public JS modules folder
-ln -sr node_modules/elevent/src/Elevent.mjs public/assets/js/modules/npm/Elevent.mjs
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
deleted file mode 100644
index 087a74c..0000000
--- a/package-lock.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "name": "vlw.se",
- "lockfileVersion": 3,
- "requires": true,
- "packages": {
- "": {
- "dependencies": {
- "elevent": "^1.0.2"
- }
- },
- "node_modules/elevent": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/elevent/-/elevent-1.0.2.tgz",
- "integrity": "sha512-ks5LBUBTg4Bpfmj99OcFAzuDGzBRDEZhTyxmq/Y3RbsdBQ4JCaIUYB0M15OBvBWgIn1BnCo4WCSmw0/YbCJliw=="
- }
- }
-}
diff --git a/package.json b/package.json
deleted file mode 100644
index 50060b0..0000000
--- a/package.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "dependencies": {
- "elevent": "^1.0.2"
- }
-}
diff --git a/public/assets/css/pages/work.css b/public/assets/css/pages/work/index.css
similarity index 100%
rename from public/assets/css/pages/work.css
rename to public/assets/css/pages/work/index.css
diff --git a/public/assets/js/pages/index.js b/public/assets/js/pages/index.js
index 45a55ba..56c54bc 100644
--- a/public/assets/js/pages/index.js
+++ b/public/assets/js/pages/index.js
@@ -1,5 +1,3 @@
-import { Elevent } from "/assets/js/modules/npm/Elevent.mjs";
-
// Click to copy email button
{
const EMAIL_CPY_ANIM_DUR_MSECONDS = 1000;
@@ -55,7 +53,7 @@ import { Elevent } from "/assets/js/modules/npm/Elevent.mjs";
}, EMAIL_CPY_ANIM_DUR_MSECONDS + 100);
}
- new Elevent("click", document.querySelector(".email"), async () => {
+ document.querySelector(".email").addEventListener("click", async () => {
try {
await navigator.clipboard.writeText("victor@vlw.se");
diff --git a/public/assets/js/shell.js b/public/assets/js/shell.js
index cbda87f..0fd8c22 100644
--- a/public/assets/js/shell.js
+++ b/public/assets/js/shell.js
@@ -1,59 +1,42 @@
-import { Elevent } from "/assets/js/modules/npm/Elevent.mjs";
-
+const DEBOUNCE_TIMEOUT_MS = 100;
const CLASSNAME_SEARCHBOX_ACTIVE = "searchboxActive";
-// Set global Vegvisir naviation delay for page transition effect
-VV.delay = 100;
-
-// Handle search box open/close buttons
-{
- // Open search box
- new Elevent("click", document.querySelector(".searchbox-open"), () => {
- document.querySelector("header").classList.add(CLASSNAME_SEARCHBOX_ACTIVE);
- // Select searchbox inner input element
- document.querySelector("searchbox input").focus();
- });
-
- // Close searchbox
- new Elevent("click", document.querySelector(".searchbox-close"), () => {
- // Disable search button interaction while animation is running
- // This is required to prevent conflicts with the :hover "peak" transformation
- const searchButtonElement = document.querySelector("header button.search");
- const transformDuration = parseInt(window.getComputedStyle(searchButtonElement).getPropertyValue("--transform-duration"));
- searchButtonElement.style.setProperty("pointer-events", "none");
-
- document.querySelector("header").classList.remove(CLASSNAME_SEARCHBOX_ACTIVE);
-
- // Wait for the transform animation to finish
- setTimeout(() => searchButtonElement.style.removeProperty("pointer-events"), transformDuration);
- });
-}
-
-// Root shell navigation event handlers
-{
- // On all top shell navigations
- new Elevent(VV.EVENT.START, VV.shell, () => {
- // Close searchbox on top shell navigations
- document.querySelector("header").classList.remove(CLASSNAME_SEARCHBOX_ACTIVE);
- });
-}
-
-// Handle search logic
-{
- const searchResultsElement = document.querySelector("search-results");
-
- document.querySelector("header input[type='search']").addEventListener("input", (event) => {
- // Debounce user input
- clearTimeout(event.target._throttle);
- event.target._throttle = setTimeout(() => {
- // Navigate search-results element on user input
- new VV(searchResultsElement).navigate(`/search?query=${event.target.value}`);
- }, 100);
- });
-}
-
// Navigate to the start page if the logo in the header is clicked
document.querySelector("header .logo").addEventListener("click", () => new VV().navigate("/"));
// Scroll page to top on navigation
-VV.shell.addEventListener(VV.EVENT.FINISH, () => window.scrollTo({ top: 0 }));
\ No newline at end of file
+VV.shell.addEventListener(VV.EVENT.FINISH, () => window.scrollTo({ top: 0 }));
+
+// Open search box
+document.querySelector(".searchbox-open").addEventListener("click", () => {
+ document.querySelector("header").classList.add(CLASSNAME_SEARCHBOX_ACTIVE);
+ // Select searchbox inner input element
+ document.querySelector("searchbox input").focus();
+});
+
+// Close searchbox
+document.querySelector(".searchbox-close").addEventListener("click", () => {
+ // Disable search button interaction while animation is running
+ // This is required to prevent conflicts with the :hover "peak" transformation
+ const searchButtonElement = document.querySelector("header button.search");
+ const transformDuration = parseInt(window.getComputedStyle(searchButtonElement).getPropertyValue("--transform-duration"));
+ searchButtonElement.style.setProperty("pointer-events", "none");
+
+ document.querySelector("header").classList.remove(CLASSNAME_SEARCHBOX_ACTIVE);
+
+ // Wait for the transform animation to finish
+ setTimeout(() => searchButtonElement.style.removeProperty("pointer-events"), transformDuration);
+});
+
+// Close searchbox on top shell navigations
+VV.shell.addEventListener(VV.EVENT.START, () => document.querySelector("header").classList.remove(CLASSNAME_SEARCHBOX_ACTIVE));
+
+// Handle search logic
+document.querySelector("header input[type='search']").addEventListener("input", (event) => {
+ // Debounce user input
+ clearTimeout(event.target._throttle);
+ event.target._throttle = setTimeout(() => {
+ // Navigate search-results element on user input
+ new VV(document.querySelector("search-results")).navigate(`/search?query=${event.target.value}`);
+ }, DEBOUNCE_TIMEOUT_MS);
+});
\ No newline at end of file
diff --git a/public/search.php b/public/search.php
index 5e3bb1b..37bed99 100644
--- a/public/search.php
+++ b/public/search.php
@@ -33,7 +33,7 @@