diff options
Diffstat (limited to 'includes/search.php')
-rw-r--r-- | includes/search.php | 529 |
1 files changed, 529 insertions, 0 deletions
diff --git a/includes/search.php b/includes/search.php new file mode 100644 index 0000000..037b6de --- /dev/null +++ b/includes/search.php @@ -0,0 +1,529 @@ +<div id="global-search-container" style="display: none; position: fixed; z-index: 99999999;background-color: rgba(0, 0, 0, .75); inset: 0; backdrop-filter: blur(5px); -webkit-backdrop-filter: blur(5px); align-items: center; justify-content: center;"> + <div id="global-search-box" style="background: rgba(50, 50, 50, .5); color: white; backdrop-filter: blur(5px); -webkit-backdrop-filter: blur(5px); border-radius: 15px; width: 768px; max-width: 90vw;"> + <div id="global-search-input-container" onclick="document.getElementById('global-search-input').focus();" style="cursor:text;padding: 10px 50px; font-size: 22px; border: 1px solid rgba(100, 100, 100, .5); border-top-left-radius: 15px; border-top-right-radius: 15px; width: 768px; max-width: 90vw;"> + <span onchange="globalSearchPlaceholder(); globalSearch();" onkeyup="globalSearchPlaceholder(); globalSearch();" onkeydown="globalSearchPlaceholder(); globalSearch();" type="text" id="global-search-input" style="display:inline-block; background: transparent; color: white; border: none;" spellcheck="false" contenteditable="true"></span><span id="global-search-placeholder" style="opacity:.5;">Website Search</span><span id="global-search-autocomplete" style="opacity:.5;"></span><span id="global-search-action" style="opacity:.5;font-size:16px;"></span> + </div> + <div id="global-search-results" style="border-bottom-left-radius: 15px; border-bottom-right-radius: 15px; border-top-style: none; border-right: 1px solid rgba(100, 100, 100, .5); border-bottom: 1px solid rgba(100, 100, 100, .5); border-left: 1px solid rgba(100, 100, 100, .5); width: 768px; max-width: 90vw; height: 400px; max-height: calc(90vh - 60px);"> + <div id="global-search-intro" style="display: flex; align-items: center; justify-content: center; height: 100%; text-align: center;"> + <div style="opacity: .5;"> + <img src="/assets/logo/logo.png" style="width: 64px; height: 64px; margin-bottom: 10px;"> + <p>Start typing to search for something on the entire website.</p> + </div> + </div> + <div id="global-search-list" style="overflow: auto; display: flex; align-items: center; justify-content: center; height: 100%; text-align: center;"></div> + </div> + </div> +</div> + +<script src="/assets/editor/fuse.js"></script> + +<!--suppress JSUnresolvedFunction --> +<script> + const pages_list = JSON.parse(atob(`<?php + + $base = array_values(array_filter(scandir($_SERVER['DOCUMENT_ROOT'] . "/pages"), function ($i) { + return !str_starts_with($i, ".") && $i !== "page.php" && $i !== "api.php" && $i !== "demo.php" && $i !== "logout.php" && $i !== "edit.php" && $i !== "edit-private.php" && $i !== "app.php" && !str_ends_with($i, ".bak.php") && !str_ends_with($i, ".old.php") && !str_ends_with($i, "-dev.php"); + })); + $list = array_values(array_filter(array_map(function ($i) { + global $pages; + global $isLoggedIn; + + if (in_array(substr($i, 0, strlen($i) - 4), array_keys($pages)) && $pages[substr($i, 0, strlen($i) - 4)]["admin"] && !$isLoggedIn) { + return null; + } else { + return [ + 'name' => in_array(substr($i, 0, strlen($i) - 4), array_keys($pages)) ? $pages[substr($i, 0, strlen($i) - 4)]["name"] : substr($i, 0, strlen($i) - 4), + 'description' => in_array(substr($i, 0, strlen($i) - 4), array_keys($pages)) ? ($pages[substr($i, 0, strlen($i) - 4)]["short"] ?? $pages[substr($i, 0, strlen($i) - 4)]["name"]) : substr($i, 0, strlen($i) - 4), + 'url' => "/-/" . substr($i, 0, strlen($i) - 4), + 'icon' => "/assets/icons/" . (file_exists($_SERVER['DOCUMENT_ROOT'] . "/assets/icons/" . substr($i, 0, strlen($i) - 4) . ".svg") ? substr($i, 0, strlen($i) - 4) : "") . ".svg", + 'invert' => true + ]; + } + }, $base), function ($i) { + return isset($i); + })); + + echo(base64_encode(json_encode($list))); + + ?>`)); + const pages = new Fuse(pages_list, { + includeScore: true, + keys: [ + { + name: 'name', + weight: 0.7 + }, + { + name: 'description', + weight: 0.5 + }, + { + name: 'url', + weight: 0.2 + } + ] + }) + + const ponies_list = JSON.parse(atob(`<?php + + $base = array_values(scoreOrderGlobal()); + $list = array_map(function ($i) { + return [ + 'name' => $i["display_name"] ?? $i["name"], + 'description' => file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$i[_system]-$i[id]-content.html") ? strip_tags(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$i[_system]-$i[id]-content.html")) : "", + 'url' => '/' . $i["name"], + 'icon' => "/assets/uploads/pt" . (file_exists($_SERVER['DOCUMENT_ROOT'] . "/assets/uploads/pt-" . resolveMember($i['name']) . ".png") ? "-" . $i['name'] : "") . ".png", + 'invert' => false + ]; + }, $base); + + echo(base64_encode(json_encode($list))); + + ?>`)); + const ponies = new Fuse(ponies_list, { + includeScore: true, + keys: [ + { + name: 'name', + weight: 1 + }, + { + name: 'description', + weight: 0.7 + }, + { + name: 'url', + weight: 0.5 + } + ] + }) + + const toys_list = JSON.parse(atob(`<?php + + global $isLoggedIn; + + if ($isLoggedIn) { + $base = array_values(json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/toys.json"), true)); + $list = array_map(function ($i) { + return [ + 'name' => $i["name"], + 'description' => $i["description"], + 'icon' => "/assets/icons/toys.svg", + 'url' => "/-/toys/$i[id]", + 'invert' => true + ]; + }, $base); + + echo(base64_encode(json_encode($list))); + } else { + echo(base64_encode(json_encode([]))); + } + + ?>`)); + const toys = new Fuse(toys_list, { + includeScore: true, + keys: [ + { + name: 'name', + weight: 1 + }, + { + name: 'description', + weight: 0.7 + }, + { + name: 'url', + weight: 0.5 + } + ] + }) + + const actions_list = JSON.parse(atob(`<?php + + global $isLoggedIn; + + if ($isLoggedIn) { + $base = array_values(json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/actions.json"), true)); + $list = array_map(function ($i) { + return [ + 'name' => $i["name"], + 'description' => $i["description"], + 'icon' => "/assets/icons/actions.svg", + 'url' => "/-/actions/$i[id]", + 'invert' => true + ]; + }, $base); + + echo(base64_encode(json_encode($list))); + } else { + echo(base64_encode(json_encode([]))); + } + + ?>`)); + const actions = new Fuse(actions_list, { + includeScore: true, + keys: [ + { + name: 'name', + weight: 1 + }, + { + name: 'description', + weight: 0.7 + }, + { + name: 'url', + weight: 0.5 + } + ] + }) + + const documents_list = JSON.parse(atob(`<?php + + global $isLoggedIn; + + if ($isLoggedIn) { + $base = array_values(array_map(function ($i) { + return [ + "id" => substr($i, 0, -5), + ...(json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/docs/" . $i), true) ?? []) + ]; + }, array_filter(scandir($_SERVER['DOCUMENT_ROOT'] . "/includes/data/docs"), function ($i) { + return !str_starts_with($i, ".") && str_ends_with($i, ".json"); + }))); + $list = array_map(function ($i) { + return [ + 'name' => $i["name"], + 'content' => strip_tags($i["contents"]), + 'category' => $i["category"], + 'icon' => "/assets/icons/docs.svg", + 'url' => "/-/docs/$i[id]", + 'invert' => true + ]; + }, $base); + + echo(base64_encode(json_encode($list))); + } else { + echo(base64_encode(json_encode([]))); + } + + ?>`)); + const documents = new Fuse(documents_list, { + includeScore: true, + keys: [ + { + name: 'name', + weight: 1 + }, + { + name: 'category', + weight: 0.7 + }, + { + name: 'content', + weight: 0.4 + }, + { + name: 'url', + weight: 0.5 + } + ] + }) + + window.shiftPresses = 0; + window.searchBoxOpen = false; + window.lastKnownSearchQuery = null; + + function toggleGlobalSearch() { + window.shiftPresses = 0; + console.log("Search!"); + + if (document.getElementById("global-search-container").style.display === "none") { + document.getElementById("global-search-container").style.display = "flex"; + document.getElementById("global-search-list").style.display = "none"; + document.getElementById("global-search-intro").style.display = "flex"; + document.getElementById("global-search-input").focus(); + document.getElementById("global-search-input").value = ""; + window.searchBoxOpen = true; + } else if (document.getElementById("global-search-container").style.display === "flex") { + document.getElementById("global-search-container").style.display = "none"; + window.searchBoxOpen = false; + } + } + + function globalSearchPlaceholder() { + if (document.getElementById("global-search-input").innerText === "") { + document.getElementById("global-search-placeholder").style.display = ""; + document.getElementById("global-search-autocomplete").innerText = ""; + document.getElementById("global-search-action").innerText = ""; + } else { + document.getElementById("global-search-placeholder").style.display = "none"; + } + } + + function globalSearchAutocomplete(firstResult) { + let querySearch = document.getElementById("global-search-input").innerText.toLowerCase(); + let firstResultSearch = firstResult.name.toLowerCase(); + let isAutocomplete = true; + + if (firstResultSearch.length > querySearch.length) { + let count = 0; + + for (let i = 0; i < querySearch.length; i++) { + if (querySearch.substring(i, i + 1) === firstResultSearch.substring(i, i + 1)) { + count++; + } else { + isAutocomplete = false; + } + } + + if (isAutocomplete) { + document.getElementById("global-search-autocomplete").innerText = firstResult.name.substring(count); + document.getElementById("global-search-action").innerText = " — View page"; + } else { + document.getElementById("global-search-autocomplete").innerText = ""; + document.getElementById("global-search-action").innerText = " — " + firstResult.name; + } + } else { + document.getElementById("global-search-autocomplete").innerText = ""; + document.getElementById("global-search-action").innerText = ""; + } + } + + function globalSearch() { + let query = document.getElementById("global-search-input").innerText.trim(); + + if (query !== lastKnownSearchQuery) { + window.lastKnownSearchQuery = query; + } else { + return; + } + + let results, scores; + let categories = []; + console.log("Query:", query); + + if (query.trim() === "") { + document.getElementById("global-search-list").style.display = "none"; + document.getElementById("global-search-intro").style.display = "flex"; + return; + } else { + document.getElementById("global-search-list").style.display = "block"; + document.getElementById("global-search-intro").style.display = "none"; + } + + results = ponies.search(query).map((i) => { + i.item.score = i.score; + return i.item; + }); + scores = results.map((i) => { return i.score; }); + let results_ponies = { + category: { + name: "Ponies", + id: "ponies" + }, + average: scores.length > 0 ? scores.reduce((a, b) => { return a + b; }) / results.length : 1, + results + }; + + results = pages.search(query).map((i) => { + i.item.score = i.score; + return i.item; + }); + scores = results.map((i) => { return i.score; }); + let results_pages = { + category: { + name: "Pages", + id: "pages" + }, + average: scores.length > 0 ? scores.reduce((a, b) => { return a + b; }) / results.length : 1, + results + }; + + results = actions.search(query).map((i) => { + i.item.score = i.score; + return i.item; + }); + scores = results.map((i) => { return i.score; }); + let results_actions = { + category: { + name: "Actions", + id: "actions" + }, + average: scores.length > 0 ? scores.reduce((a, b) => { return a + b; }) / results.length : 1, + results + }; + + results = documents.search(query).map((i) => { + i.item.score = i.score; + return i.item; + }); + scores = results.map((i) => { return i.score; }); + let results_documents = { + category: { + name: "Documents", + id: "docs" + }, + average: scores.length > 0 ? scores.reduce((a, b) => { return a + b; }) / results.length : 1, + results + }; + + results = toys.search(query).map((i) => { + i.item.score = i.score; + return i.item; + }); + scores = results.map((i) => { return i.score; }); + let results_toys = { + category: { + name: "Toys", + id: "toys" + }, + average: scores.length > 0 ? scores.reduce((a, b) => { return a + b; }) / results.length : 1, + results + }; + + window.allSearchResults = []; + categories.push(results_ponies, results_pages, results_documents, results_actions, results_toys); + categories = categories.sort((a, b) => { + return a.average - b.average; + }).filter((i) => i.results.length > 0); + + console.log(categories); + + index = -1; + document.getElementById("global-search-list").innerHTML = categories.map((i) => ` + <div class="global-search-category"> + <div class="global-search-category-header">${i.category.name}</div> + <div class="global-search-category-items"> + ${i.results.map((j) => { index++; allSearchResults.push(j); return ` + <a href="${j.url}" class="global-search-item ${index === 0 ? 'selected' : ''}" id="global-search-item-${index}"> + <img src="${j.icon}" class="global-search-item-icon" ${j['invert'] ? 'style="filter:invert(1) brightness(5);"' : ""}> + <span class="global-search-item-title">${j.name}</span> + </a> + ` }).join("")} + </div> + </div> + `).join(""); + + globalSearchAutocomplete(allSearchResults[0]); + + console.log("================================="); + } + + document.onkeydown = (event) => { + if (window.searchBoxOpen) { + console.log(event); + + if (event.key === "Escape") { + if (document.getElementById("global-search-input").innerText.trim() === "") { + toggleGlobalSearch(); + } else { + document.getElementById("global-search-input").innerText = ""; + globalSearch(); + } + } + + if (event.key === "ArrowDown") { + let id = parseInt(document.querySelector(".global-search-item.selected").id.split("-")[3]); + + if (document.getElementById("global-search-item-" + (id + 1)) !== null) { + document.getElementById("global-search-item-" + id).classList.remove("selected"); + document.getElementById("global-search-item-" + (id + 1)).classList.add("selected"); + document.getElementById("global-search-item-" + (id + 1)).scrollIntoView({ + block: "center", + inline: "center" + }); + + globalSearchAutocomplete(allSearchResults[id + 1]); + } + + event.preventDefault(); + } + + if (event.key === "ArrowUp") { + let id = parseInt(document.querySelector(".global-search-item.selected").id.split("-")[3]); + + if (document.getElementById("global-search-item-" + (id - 1)) !== null) { + document.getElementById("global-search-item-" + id).classList.remove("selected"); + document.getElementById("global-search-item-" + (id - 1)).classList.add("selected"); + document.getElementById("global-search-item-" + (id - 1)).scrollIntoView({ + block: "center", + inline: "center" + }); + + globalSearchAutocomplete(allSearchResults[id - 1]); + } + + event.preventDefault(); + } + + if (event.key === "Enter") { + document.querySelector(".global-search-item.selected").click(); + + event.preventDefault(); + } + } + + if (event.key === "Shift") { + window.shiftPresses++; + } + + if ((event.code === "Space" && event.ctrlKey) || window.shiftPresses === 2) { + toggleGlobalSearch(); + } + } + + document.getElementById("global-search-container").onclick = (event) => { + if (event.target === document.getElementById("global-search-container")) toggleGlobalSearch(); + } + + setInterval(() => { + window.shiftPresses = 0; + }, 500); +</script> + +<style> + .global-search-category { + text-align: left; + padding: 10px 20px; + } + + .global-search-category-header { + font-size: 12px; + opacity: .5; + margin-bottom: 5px; + margin-top: 10px; + } + + .global-search-item-icon { + vertical-align: middle; + width: 24px; + height: 24px; + margin-right: 5px; + } + + .global-search-item-title { + vertical-align: middle; + } + + .global-search-item { + text-decoration: none; + display: block; + color: white !important; + border-radius: 10px; + padding: 3px 5px; + height: 32px; + } + + .global-search-item:hover, .global-search-item:focus, .global-search-item.selected { + background-color: rgba(255, 255, 255, .1); + } + + .global-search-item:active { + background-color: rgba(255, 255, 255, .2); + } + + #global-search-container * { + outline: none !important; + } +</style>
\ No newline at end of file |