Browse Source

Initial implentation

Michael Shamoon 2 years ago
parent
commit
fe770c3864
2 changed files with 123 additions and 0 deletions
  1. 93 0
      src/components/search.jsx
  2. 30 0
      src/pages/index.jsx

+ 93 - 0
src/components/search.jsx

@@ -0,0 +1,93 @@
+import { useTranslation } from "react-i18next";
+import { useEffect, useState, useRef } from "react";
+import classNames from "classnames";
+
+export default function Search({bookmarks, services, searchString, setSearchString, isOpen, close}) {
+  const { t, i18n } = useTranslation();
+  const all = [...bookmarks.map(bg => bg.bookmarks).flat(), ...services.map(sg => sg.services).flat()];
+
+  const searchField = useRef();
+
+  const [results, setResults] = useState([]);
+  const [currentItemIndex, setCurrentItemIndex] = useState(null);
+
+  function handleSearchChange(event) {
+    setSearchString(event.target.value.toLowerCase())
+  }
+
+  function handleSearchKeyDown(event) {
+    if (event.key === "Escape") {
+      setSearchString("");
+      close(false);
+    } else if (event.key === "Enter" && results.length) {
+      setSearchString("");
+      close(false);
+      const result = results[currentItemIndex];
+      console.log("go to", result);
+      window.open(result.href, '_blank');
+    } else if (event.key == "ArrowDown" && results[currentItemIndex + 1]) {
+      setCurrentItemIndex(currentItemIndex + 1);
+      event.preventDefault();
+    } else if (event.key == "ArrowUp" && currentItemIndex > 0) {
+      setCurrentItemIndex(currentItemIndex - 1);
+      event.preventDefault();
+    }
+  }
+
+  useEffect(() => {
+    if (searchString.length === 0) setResults([]);
+    else {
+      const newResults = all.filter(r => r.name.toLowerCase().includes(searchString));
+      setResults(newResults);
+      if (newResults.length) {
+        setCurrentItemIndex(0);
+      }
+    }
+  }, [searchString])
+
+
+  const [hidden, setHidden] = useState(true);
+  useEffect(() => {
+    if (isOpen) {
+      searchField.current.focus();
+      setHidden(false);
+    } else {
+      setTimeout(() => {
+        setHidden(true);
+      }, 300); // disable on close
+    }
+  }, [isOpen])
+
+  return (
+    <div className={classNames(
+      "relative z-10 ease-in-out duration-300 transition-opacity",
+      hidden && !isOpen && "hidden",
+      !hidden && isOpen && "opacity-100",
+      !isOpen && "opacity-0",
+    )} role="dialog" aria-modal="true">
+      <div className="fixed inset-0 bg-gray-500 bg-opacity-50"></div>
+      <div className="fixed inset-0 z-10 overflow-y-auto">
+        <div className="flex min-h-full min-w-full items-start justify-center text-center">
+            <div className="mt-[10%] p-4 min-w-[50%] rounded-md font-medium text-theme-700 dark:text-theme-200 dark:hover:text-theme-300 shadow-md shadow-theme-900/10 dark:shadow-theme-900/20 bg-theme-50 dark:bg-theme-800">
+              <div className="text-center">
+                <input placeholder="Search" className="rounded-md text-xl min-w-full text-theme-700 dark:text-theme-200 bg-theme-60 dark:bg-theme-800" type="text" ref={searchField} value={searchString} onChange={handleSearchChange} onKeyDown={handleSearchKeyDown} />
+                <ul>
+                  {results.map((w, i) => {
+                    return (
+                      <li className={classNames(
+                        i === currentItemIndex && "bg-theme-60/50 dark:bg-theme-700/50",
+                        i !== currentItemIndex && "bg-theme-50/50 dark:bg-theme-800/50",
+                        "text-xl py-3 cursor-pointer text-theme-700 dark:text-theme-200 dark:hover:text-theme-300 hover:bg-theme-900/50 dark:hover:bg-theme-900/50",
+                        )} key={w.name}>
+                          {w.name}
+                      </li>)
+                  })
+                  }
+                </ul>
+                </div>
+              </div>
+          </div>
+        </div>
+      </div>
+  );
+}

+ 30 - 0
src/pages/index.jsx

@@ -21,6 +21,7 @@ import { SettingsContext } from "utils/contexts/settings";
 import { bookmarksResponse, servicesResponse, widgetsResponse } from "utils/config/api-response";
 import { bookmarksResponse, servicesResponse, widgetsResponse } from "utils/config/api-response";
 import ErrorBoundary from "components/errorboundry";
 import ErrorBoundary from "components/errorboundry";
 import themes from "utils/styles/themes";
 import themes from "utils/styles/themes";
+import Search from "components/search";
 
 
 const ThemeToggle = dynamic(() => import("components/toggles/theme"), {
 const ThemeToggle = dynamic(() => import("components/toggles/theme"), {
   ssr: false,
   ssr: false,
@@ -160,6 +161,11 @@ const headerStyles = {
   clean: "m-4 mb-0 sm:m-8 sm:mb-0",
   clean: "m-4 mb-0 sm:m-8 sm:mb-0",
 };
 };
 
 
+function handleChange(event) {
+  // this.setState({value: event.target.value});
+  console.log(event);
+}
+
 function Home({ initialSettings }) {
 function Home({ initialSettings }) {
   const { i18n } = useTranslation();
   const { i18n } = useTranslation();
   const { theme, setTheme } = useContext(ThemeContext);
   const { theme, setTheme } = useContext(ThemeContext);
@@ -188,6 +194,29 @@ function Home({ initialSettings }) {
     }
     }
   }, [i18n, settings, color, setColor, theme, setTheme]);
   }, [i18n, settings, color, setColor, theme, setTheme]);
 
 
+  const [searching, setSearching] = useState(false);
+  const [searchString, setSearchString] = useState(false);
+
+  useEffect(() => {
+    document.addEventListener('keydown', handleKeyDown);
+
+    function handleKeyDown(e) {
+      console.log(e.target.tagName, e.key, e);
+      if (e.target.tagName === "BODY") {
+        if (String.fromCharCode(e.keyCode).match(/(\w|\s)/g) && !(e. altKey || e.ctrlKey || e.metaKey || e.shiftKey)) {
+          setSearching(true);
+        } else if (e.key === "Escape") {
+          setSearchString("");
+          setSearching(false);
+        }
+      }
+    }
+
+    return function cleanup() {
+        document.removeEventListener('keydown', handleKeyDown);
+    }
+  })
+
   return (
   return (
     <>
     <>
       <Head>
       <Head>
@@ -211,6 +240,7 @@ function Home({ initialSettings }) {
             headerStyles[initialSettings.headerStyle || "underlined"]
             headerStyles[initialSettings.headerStyle || "underlined"]
           )}
           )}
         >
         >
+          <Search services={services} bookmarks={bookmarks} searchString={searchString} setSearchString={setSearchString} isOpen={searching} close={setSearching} />
           {widgets && (
           {widgets && (
             <>
             <>
               {widgets
               {widgets