summaryrefslogtreecommitdiff
path: root/includes/search.inc
diff options
context:
space:
mode:
authorMinteck <contact@minteck.org>2022-10-10 20:51:39 +0200
committerMinteck <contact@minteck.org>2022-10-10 20:51:39 +0200
commit108525534c28013cfe1897c30e4565f9893f3766 (patch)
treedd3e5132971f96ab5f05e7f3f8f6dbbf379a19bd /includes/search.inc
parent2162eaa06f7e4764eb3dcfe130ec2c711d0c62ab (diff)
downloadpluralconnect-108525534c28013cfe1897c30e4565f9893f3766.tar.gz
pluralconnect-108525534c28013cfe1897c30e4565f9893f3766.tar.bz2
pluralconnect-108525534c28013cfe1897c30e4565f9893f3766.zip
Update
Diffstat (limited to 'includes/search.inc')
-rw-r--r--includes/search.inc529
1 files changed, 529 insertions, 0 deletions
diff --git a/includes/search.inc b/includes/search.inc
new file mode 100644
index 0000000..d8e573f
--- /dev/null
+++ b/includes/search.inc
@@ -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.inc" && $i !== "api.inc" && $i !== "demo.inc" && $i !== "logout.inc" && $i !== "edit.inc" && $i !== "edit-private.inc" && $i !== "app.inc" && !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/content/$i[id].html") ? strip_tags(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/content/$i[id].html")) : "",
+ 'url' => '/' . $i["name"],
+ 'icon' => getAsset($i["system"], $i["id"], "heads"),
+ '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/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/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