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 @@ - + diff --git a/public/shell.php b/public/shell.php index 68c8644..2fd9cbc 100644 --- a/public/shell.php +++ b/public/shell.php @@ -37,7 +37,6 @@ //--> - @@ -69,8 +68,7 @@ - - + \ No newline at end of file diff --git a/public/work.php b/public/work/index.php similarity index 90% rename from public/work.php rename to public/work/index.php index e4b83e5..097590c 100644 --- a/public/work.php +++ b/public/work/index.php @@ -1,13 +1,11 @@ - +
@@ -15,7 +13,7 @@

vegvisir

-

summary() ?>

+

summary ?>

-

summary() ?>

+

summary ?>

vlw/php-mysql

-

summary() ?>

+

summary ?>

Website for iCellate Medical

-

summary() ?>

+

summary ?>

Website for GeneMate by iCellate

-

summary() ?>

+

summary ?>

Campaign pages for Deltaco AB

-

summary() ?>

+

summary ?>