From fe770c3864afaf05512e8984335950f74e79a0e7 Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue, 18 Oct 2022 23:10:44 -0700 Subject: [PATCH 01/12] Initial implentation --- src/components/search.jsx | 93 +++++++++++++++++++++++++++++++++++++++ src/pages/index.jsx | 30 +++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 src/components/search.jsx diff --git a/src/components/search.jsx b/src/components/search.jsx new file mode 100644 index 00000000..a53ab59d --- /dev/null +++ b/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 ( + + ); +} diff --git a/src/pages/index.jsx b/src/pages/index.jsx index b4eb50e1..3d6a2298 100644 --- a/src/pages/index.jsx +++ b/src/pages/index.jsx @@ -21,6 +21,7 @@ import { SettingsContext } from "utils/contexts/settings"; import { bookmarksResponse, servicesResponse, widgetsResponse } from "utils/config/api-response"; import ErrorBoundary from "components/errorboundry"; import themes from "utils/styles/themes"; +import Search from "components/search"; const ThemeToggle = dynamic(() => import("components/toggles/theme"), { ssr: false, @@ -160,6 +161,11 @@ const headerStyles = { 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 }) { const { i18n } = useTranslation(); const { theme, setTheme } = useContext(ThemeContext); @@ -188,6 +194,29 @@ function Home({ initialSettings }) { } }, [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 ( <> @@ -211,6 +240,7 @@ function Home({ initialSettings }) { headerStyles[initialSettings.headerStyle || "underlined"] )} > + {widgets && ( <> {widgets From 3249c95a74327cc7e4109c31513066e9ab8dcc73 Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Wed, 19 Oct 2022 00:26:54 -0700 Subject: [PATCH 02/12] Refactor homepage search, visual improvement --- public/locales/en/common.json | 4 ++++ src/components/search.jsx | 33 ++++++++++++++++++++------------ src/components/services/item.jsx | 2 +- src/pages/index.jsx | 6 +++--- 4 files changed, 29 insertions(+), 16 deletions(-) diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 69d88305..9724db9d 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -218,5 +218,9 @@ "cpu": "CPU", "mem": "MEM", "wait": "Please wait" + }, + "homepagesearch": { + "bookmark": "Bookmark", + "service": "Service" } } diff --git a/src/components/search.jsx b/src/components/search.jsx index a53ab59d..d6de8a30 100644 --- a/src/components/search.jsx +++ b/src/components/search.jsx @@ -1,8 +1,9 @@ import { useTranslation } from "react-i18next"; import { useEffect, useState, useRef } from "react"; import classNames from "classnames"; +import { resolveIcon } from "./services/item"; -export default function Search({bookmarks, services, searchString, setSearchString, isOpen, close}) { +export default function HomepageSearch({bookmarks, services, searchString, setSearchString, isOpen, close}) { const { t, i18n } = useTranslation(); const all = [...bookmarks.map(bg => bg.bookmarks).flat(), ...services.map(sg => sg.services).flat()]; @@ -23,7 +24,6 @@ export default function Search({bookmarks, services, searchString, setSearchStri 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); @@ -68,24 +68,33 @@ export default function Search({bookmarks, services, searchString, setSearchStri
-
-
- +
+ 0 && "rounded-t-md", + results.length === 0 && "rounded-md", + "w-full p-4 m-0 border-0 border-b border-slate-700 focus:border-slate-700 focus:outline-0 focus:ring-0 text-sm md:text-xl text-theme-700 dark:text-theme-200 bg-theme-60 dark:bg-theme-800" + )} type="text" ref={searchField} value={searchString} onChange={handleSearchChange} onKeyDown={handleSearchKeyDown} />
    - {results.map((w, i) => { + {results.map((r, i) => { return (
  • - {w.name} + i === 0 && "mt-4", + i === currentItemIndex && "bg-theme-300/50 dark:bg-theme-700/50", + "flex flex-row items-center justify-between rounded-md text-sm md:text-xl m-2 py-2 px-4 cursor-pointer text-theme-700 dark:text-theme-200 dark:hover:text-theme-300 hover:bg-theme-200/50 dark:hover:bg-theme-900/30", + )} key={r.name}> +
    +
    + {r.icon && resolveIcon(r.icon)} + {r.abbr && r.abbr} +
    + {r.name} +
    +
    {r.abbr ? t("homepagesearch.bookmark") : t("homepagesearch.service")}
  • ) }) }
-
diff --git a/src/components/services/item.jsx b/src/components/services/item.jsx index 345e2d5c..ea3bc6a0 100644 --- a/src/components/services/item.jsx +++ b/src/components/services/item.jsx @@ -8,7 +8,7 @@ import Widget from "./widget"; import Docker from "widgets/docker/component"; import { SettingsContext } from "utils/contexts/settings"; -function resolveIcon(icon) { +export function resolveIcon(icon) { // direct or relative URLs if (icon.startsWith("http") || icon.startsWith("/")) { return logo; diff --git a/src/pages/index.jsx b/src/pages/index.jsx index 3d6a2298..3b047e6e 100644 --- a/src/pages/index.jsx +++ b/src/pages/index.jsx @@ -21,7 +21,7 @@ import { SettingsContext } from "utils/contexts/settings"; import { bookmarksResponse, servicesResponse, widgetsResponse } from "utils/config/api-response"; import ErrorBoundary from "components/errorboundry"; import themes from "utils/styles/themes"; -import Search from "components/search"; +import HomepageSearch from "components/search"; const ThemeToggle = dynamic(() => import("components/toggles/theme"), { ssr: false, @@ -195,7 +195,7 @@ function Home({ initialSettings }) { }, [i18n, settings, color, setColor, theme, setTheme]); const [searching, setSearching] = useState(false); - const [searchString, setSearchString] = useState(false); + const [searchString, setSearchString] = useState(""); useEffect(() => { document.addEventListener('keydown', handleKeyDown); @@ -240,7 +240,7 @@ function Home({ initialSettings }) { headerStyles[initialSettings.headerStyle || "underlined"] )} > - + {widgets && ( <> {widgets From fa46c9a1a4e33b3f4efa58d8d17f84b190f6f560 Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Wed, 19 Oct 2022 00:54:35 -0700 Subject: [PATCH 03/12] lint --- src/components/search.jsx | 67 +++++++++++++++++++-------------------- src/pages/index.jsx | 14 +++----- 2 files changed, 37 insertions(+), 44 deletions(-) diff --git a/src/components/search.jsx b/src/components/search.jsx index d6de8a30..ba15b42a 100644 --- a/src/components/search.jsx +++ b/src/components/search.jsx @@ -1,11 +1,11 @@ import { useTranslation } from "react-i18next"; import { useEffect, useState, useRef } from "react"; import classNames from "classnames"; + import { resolveIcon } from "./services/item"; -export default function HomepageSearch({bookmarks, services, searchString, setSearchString, isOpen, close}) { - const { t, i18n } = useTranslation(); - const all = [...bookmarks.map(bg => bg.bookmarks).flat(), ...services.map(sg => sg.services).flat()]; +export default function HomepageSearch({bookmarksAndServices, searchString, setSearchString, isOpen, close}) { + const { t } = useTranslation(); const searchField = useRef(); @@ -25,10 +25,10 @@ export default function HomepageSearch({bookmarks, services, searchString, setSe close(false); const result = results[currentItemIndex]; window.open(result.href, '_blank'); - } else if (event.key == "ArrowDown" && results[currentItemIndex + 1]) { + } else if (event.key === "ArrowDown" && results[currentItemIndex + 1]) { setCurrentItemIndex(currentItemIndex + 1); event.preventDefault(); - } else if (event.key == "ArrowUp" && currentItemIndex > 0) { + } else if (event.key === "ArrowUp" && currentItemIndex > 0) { setCurrentItemIndex(currentItemIndex - 1); event.preventDefault(); } @@ -37,13 +37,13 @@ export default function HomepageSearch({bookmarks, services, searchString, setSe useEffect(() => { if (searchString.length === 0) setResults([]); else { - const newResults = all.filter(r => r.name.toLowerCase().includes(searchString)); + const newResults = bookmarksAndServices.filter(r => r.name.toLowerCase().includes(searchString)); setResults(newResults); if (newResults.length) { setCurrentItemIndex(0); } } - }, [searchString]) + }, [searchString, bookmarksAndServices]) const [hidden, setHidden] = useState(true); @@ -65,38 +65,35 @@ export default function HomepageSearch({bookmarks, services, searchString, setSe !hidden && isOpen && "opacity-100", !isOpen && "opacity-0", )} role="dialog" aria-modal="true"> -
+
-
- 0 && "rounded-t-md", - results.length === 0 && "rounded-md", - "w-full p-4 m-0 border-0 border-b border-slate-700 focus:border-slate-700 focus:outline-0 focus:ring-0 text-sm md:text-xl text-theme-700 dark:text-theme-200 bg-theme-60 dark:bg-theme-800" - )} type="text" ref={searchField} value={searchString} onChange={handleSearchChange} onKeyDown={handleSearchKeyDown} /> -
    - {results.map((r, i) => { - return ( -
  • -
    -
    - {r.icon && resolveIcon(r.icon)} - {r.abbr && r.abbr} -
    - {r.name} -
    -
    {r.abbr ? t("homepagesearch.bookmark") : t("homepagesearch.service")}
    -
  • ) - }) - } -
-
+
+ 0 && "rounded-t-md", + results.length === 0 && "rounded-md", + "w-full p-4 m-0 border-0 border-b border-slate-700 focus:border-slate-700 focus:outline-0 focus:ring-0 text-sm md:text-xl text-theme-700 dark:text-theme-200 bg-theme-60 dark:bg-theme-800" + )} type="text" ref={searchField} value={searchString} onChange={handleSearchChange} onKeyDown={handleSearchKeyDown} /> + {results.length > 0 &&
    + {results.map((r, i) => ( +
  • +
    +
    + {r.icon && resolveIcon(r.icon)} + {r.abbr && r.abbr} +
    + {r.name} +
    +
    {r.abbr ? t("homepagesearch.bookmark") : t("homepagesearch.service")}
    +
  • + ))} +
}
+
); } diff --git a/src/pages/index.jsx b/src/pages/index.jsx index 3b047e6e..efedf83d 100644 --- a/src/pages/index.jsx +++ b/src/pages/index.jsx @@ -161,11 +161,6 @@ const headerStyles = { 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 }) { const { i18n } = useTranslation(); const { theme, setTheme } = useContext(ThemeContext); @@ -179,6 +174,8 @@ function Home({ initialSettings }) { const { data: services } = useSWR("/api/services"); const { data: bookmarks } = useSWR("/api/bookmarks"); const { data: widgets } = useSWR("/api/widgets"); + + const bookmarksAndServices = [...bookmarks.map(bg => bg.bookmarks).flat(), ...services.map(sg => sg.services).flat()] useEffect(() => { if (settings.language) { @@ -198,10 +195,7 @@ function Home({ initialSettings }) { const [searchString, setSearchString] = useState(""); 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); @@ -212,6 +206,8 @@ function Home({ initialSettings }) { } } + document.addEventListener('keydown', handleKeyDown); + return function cleanup() { document.removeEventListener('keydown', handleKeyDown); } @@ -240,7 +236,7 @@ function Home({ initialSettings }) { headerStyles[initialSettings.headerStyle || "underlined"] )} > - + {widgets && ( <> {widgets From db9633496b17612ba5bdeb356174fa8a62f9749b Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Wed, 19 Oct 2022 09:04:34 -0700 Subject: [PATCH 04/12] Handle clicking items --- src/components/search.jsx | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/src/components/search.jsx b/src/components/search.jsx index ba15b42a..596efc45 100644 --- a/src/components/search.jsx +++ b/src/components/search.jsx @@ -12,17 +12,20 @@ export default function HomepageSearch({bookmarksAndServices, searchString, setS const [results, setResults] = useState([]); const [currentItemIndex, setCurrentItemIndex] = useState(null); + function resetAndClose() { + setSearchString(""); + close(false); + } + function handleSearchChange(event) { setSearchString(event.target.value.toLowerCase()) } function handleSearchKeyDown(event) { if (event.key === "Escape") { - setSearchString(""); - close(false); + resetAndClose(); } else if (event.key === "Enter" && results.length) { - setSearchString(""); - close(false); + resetAndClose(); const result = results[currentItemIndex]; window.open(result.href, '_blank'); } else if (event.key === "ArrowDown" && results[currentItemIndex + 1]) { @@ -34,6 +37,10 @@ export default function HomepageSearch({bookmarksAndServices, searchString, setS } } + function handleItemClick() { + resetAndClose(); + } + useEffect(() => { if (searchString.length === 0) setResults([]); else { @@ -76,18 +83,20 @@ export default function HomepageSearch({bookmarksAndServices, searchString, setS )} type="text" ref={searchField} value={searchString} onChange={handleSearchChange} onKeyDown={handleSearchKeyDown} /> {results.length > 0 && } From ba4a1eb6460f1035e10b801c53a9d30d13b82582 Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Wed, 19 Oct 2022 13:55:18 -0700 Subject: [PATCH 05/12] Refactor, better handle mouseover --- src/components/{search.jsx => quicklaunch.jsx} | 10 +++++++--- src/pages/index.jsx | 4 ++-- 2 files changed, 9 insertions(+), 5 deletions(-) rename src/components/{search.jsx => quicklaunch.jsx} (92%) diff --git a/src/components/search.jsx b/src/components/quicklaunch.jsx similarity index 92% rename from src/components/search.jsx rename to src/components/quicklaunch.jsx index 596efc45..6fe15c57 100644 --- a/src/components/search.jsx +++ b/src/components/quicklaunch.jsx @@ -4,7 +4,7 @@ import classNames from "classnames"; import { resolveIcon } from "./services/item"; -export default function HomepageSearch({bookmarksAndServices, searchString, setSearchString, isOpen, close}) { +export default function QuickLaunch({bookmarksAndServices, searchString, setSearchString, isOpen, close}) { const { t } = useTranslation(); const searchField = useRef(); @@ -37,6 +37,10 @@ export default function HomepageSearch({bookmarksAndServices, searchString, setS } } + function handleItemHover(event) { + setCurrentItemIndex(parseInt(event.target?.dataset?.index)); + } + function handleItemClick() { resetAndClose(); } @@ -84,9 +88,9 @@ export default function HomepageSearch({bookmarksAndServices, searchString, setS {results.length > 0 &&
    {results.map((r, i) => (
  • -
    diff --git a/src/pages/index.jsx b/src/pages/index.jsx index efedf83d..a1ce96d5 100644 --- a/src/pages/index.jsx +++ b/src/pages/index.jsx @@ -21,7 +21,7 @@ import { SettingsContext } from "utils/contexts/settings"; import { bookmarksResponse, servicesResponse, widgetsResponse } from "utils/config/api-response"; import ErrorBoundary from "components/errorboundry"; import themes from "utils/styles/themes"; -import HomepageSearch from "components/search"; +import QuickLaunch from "components/quicklaunch"; const ThemeToggle = dynamic(() => import("components/toggles/theme"), { ssr: false, @@ -236,7 +236,7 @@ function Home({ initialSettings }) { headerStyles[initialSettings.headerStyle || "underlined"] )} > - + {widgets && ( <> {widgets From b5410eea12bcb0d8236f066ec2315ed8f510f0e0 Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Wed, 19 Oct 2022 14:23:51 -0700 Subject: [PATCH 06/12] fix quicklaunch hover bug on open --- src/components/quicklaunch.jsx | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/components/quicklaunch.jsx b/src/components/quicklaunch.jsx index 6fe15c57..f61a14e0 100644 --- a/src/components/quicklaunch.jsx +++ b/src/components/quicklaunch.jsx @@ -12,6 +12,11 @@ export default function QuickLaunch({bookmarksAndServices, searchString, setSear const [results, setResults] = useState([]); const [currentItemIndex, setCurrentItemIndex] = useState(null); + function openCurrentItem() { + const result = results[currentItemIndex]; + window.open(result.href, '_blank'); + } + function resetAndClose() { setSearchString(""); close(false); @@ -26,8 +31,7 @@ export default function QuickLaunch({bookmarksAndServices, searchString, setSear resetAndClose(); } else if (event.key === "Enter" && results.length) { resetAndClose(); - const result = results[currentItemIndex]; - window.open(result.href, '_blank'); + openCurrentItem(); } else if (event.key === "ArrowDown" && results[currentItemIndex + 1]) { setCurrentItemIndex(currentItemIndex + 1); event.preventDefault(); @@ -43,6 +47,7 @@ export default function QuickLaunch({bookmarksAndServices, searchString, setSear function handleItemClick() { resetAndClose(); + openCurrentItem(); } useEffect(() => { @@ -85,22 +90,22 @@ export default function QuickLaunch({bookmarksAndServices, searchString, setSear results.length === 0 && "rounded-md", "w-full p-4 m-0 border-0 border-b border-slate-700 focus:border-slate-700 focus:outline-0 focus:ring-0 text-sm md:text-xl text-theme-700 dark:text-theme-200 bg-theme-60 dark:bg-theme-800" )} type="text" ref={searchField} value={searchString} onChange={handleSearchChange} onKeyDown={handleSearchKeyDown} /> - {results.length > 0 &&
      + {results.length > 0 && } From 484d69a4b571213d26c5488aefc9199329720b41 Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Wed, 19 Oct 2022 14:29:30 -0700 Subject: [PATCH 07/12] services should come first --- src/components/quicklaunch.jsx | 6 +++--- src/pages/index.jsx | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/quicklaunch.jsx b/src/components/quicklaunch.jsx index f61a14e0..9051c599 100644 --- a/src/components/quicklaunch.jsx +++ b/src/components/quicklaunch.jsx @@ -4,7 +4,7 @@ import classNames from "classnames"; import { resolveIcon } from "./services/item"; -export default function QuickLaunch({bookmarksAndServices, searchString, setSearchString, isOpen, close}) { +export default function QuickLaunch({servicesAndBookmarks, searchString, setSearchString, isOpen, close}) { const { t } = useTranslation(); const searchField = useRef(); @@ -53,13 +53,13 @@ export default function QuickLaunch({bookmarksAndServices, searchString, setSear useEffect(() => { if (searchString.length === 0) setResults([]); else { - const newResults = bookmarksAndServices.filter(r => r.name.toLowerCase().includes(searchString)); + const newResults = servicesAndBookmarks.filter(r => r.name.toLowerCase().includes(searchString)); setResults(newResults); if (newResults.length) { setCurrentItemIndex(0); } } - }, [searchString, bookmarksAndServices]) + }, [searchString, servicesAndBookmarks]) const [hidden, setHidden] = useState(true); diff --git a/src/pages/index.jsx b/src/pages/index.jsx index a1ce96d5..400d7eb5 100644 --- a/src/pages/index.jsx +++ b/src/pages/index.jsx @@ -175,7 +175,7 @@ function Home({ initialSettings }) { const { data: bookmarks } = useSWR("/api/bookmarks"); const { data: widgets } = useSWR("/api/widgets"); - const bookmarksAndServices = [...bookmarks.map(bg => bg.bookmarks).flat(), ...services.map(sg => sg.services).flat()] + const servicesAndBookmarks = [...services.map(sg => sg.services).flat(), ...bookmarks.map(bg => bg.bookmarks).flat()] useEffect(() => { if (settings.language) { @@ -236,7 +236,7 @@ function Home({ initialSettings }) { headerStyles[initialSettings.headerStyle || "underlined"] )} > - + {widgets && ( <> {widgets From a1788b01c37715b3c17df5cfc701dc38a94ad387 Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Wed, 19 Oct 2022 17:10:01 -0700 Subject: [PATCH 08/12] Show description in quicklook --- src/components/quicklaunch.jsx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/components/quicklaunch.jsx b/src/components/quicklaunch.jsx index 9051c599..4a3f345e 100644 --- a/src/components/quicklaunch.jsx +++ b/src/components/quicklaunch.jsx @@ -42,7 +42,7 @@ export default function QuickLaunch({servicesAndBookmarks, searchString, setSear } function handleItemHover(event) { - setCurrentItemIndex(parseInt(event.target?.dataset?.index)); + setCurrentItemIndex(parseInt(event.target?.dataset?.index, 10)); } function handleItemClick() { @@ -84,7 +84,7 @@ export default function QuickLaunch({servicesAndBookmarks, searchString, setSear
      -
      +
      0 && "rounded-t-md", results.length === 0 && "rounded-md", @@ -93,7 +93,7 @@ export default function QuickLaunch({servicesAndBookmarks, searchString, setSear {results.length > 0 &&
        {results.map((r, i) => (
      • -
      - {r.name} +
      + {r.name} + {r.description && {r.description}} +
      -
      {r.abbr ? t("homepagesearch.bookmark") : t("homepagesearch.service")}
      +
      {r.abbr ? t("homepagesearch.bookmark") : t("homepagesearch.service")}
      ))} From 5abe13c7261baf4eb2c2333fd527d88064841982 Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Wed, 19 Oct 2022 20:16:29 -0700 Subject: [PATCH 09/12] Handle click to close, close animation timing --- src/components/quicklaunch.jsx | 33 +++++++++++++++++++++------------ src/pages/index.jsx | 2 +- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/components/quicklaunch.jsx b/src/components/quicklaunch.jsx index 4a3f345e..d40e50d0 100644 --- a/src/components/quicklaunch.jsx +++ b/src/components/quicklaunch.jsx @@ -1,5 +1,5 @@ import { useTranslation } from "react-i18next"; -import { useEffect, useState, useRef } from "react"; +import { useEffect, useState, useRef, useCallback } from "react"; import classNames from "classnames"; import { resolveIcon } from "./services/item"; @@ -17,10 +17,12 @@ export default function QuickLaunch({servicesAndBookmarks, searchString, setSear window.open(result.href, '_blank'); } - function resetAndClose() { - setSearchString(""); + const closeAndReset = useCallback(() => { close(false); - } + setTimeout(() => { + setSearchString(""); + }, 200); // delay a little for animations + }, [close, setSearchString]); function handleSearchChange(event) { setSearchString(event.target.value.toLowerCase()) @@ -28,9 +30,9 @@ export default function QuickLaunch({servicesAndBookmarks, searchString, setSear function handleSearchKeyDown(event) { if (event.key === "Escape") { - resetAndClose(); + closeAndReset(); } else if (event.key === "Enter" && results.length) { - resetAndClose(); + closeAndReset(); openCurrentItem(); } else if (event.key === "ArrowDown" && results[currentItemIndex + 1]) { setCurrentItemIndex(currentItemIndex + 1); @@ -46,7 +48,7 @@ export default function QuickLaunch({servicesAndBookmarks, searchString, setSear } function handleItemClick() { - resetAndClose(); + closeAndReset(); openCurrentItem(); } @@ -59,20 +61,27 @@ export default function QuickLaunch({servicesAndBookmarks, searchString, setSear setCurrentItemIndex(0); } } - }, [searchString, servicesAndBookmarks]) + }, [searchString, servicesAndBookmarks]); const [hidden, setHidden] = useState(true); useEffect(() => { + function handleBackdropClick(event) { + if (event.target?.tagName === "DIV") closeAndReset(); + } + if (isOpen) { searchField.current.focus(); + document.body.addEventListener('click', handleBackdropClick); setHidden(false); } else { + document.body.removeEventListener('click', handleBackdropClick); setTimeout(() => { setHidden(true); }, 300); // disable on close } - }, [isOpen]) + + }, [isOpen, closeAndReset]); return (
      -
      + 0 && "rounded-t-md", results.length === 0 && "rounded-md", "w-full p-4 m-0 border-0 border-b border-slate-700 focus:border-slate-700 focus:outline-0 focus:ring-0 text-sm md:text-xl text-theme-700 dark:text-theme-200 bg-theme-60 dark:bg-theme-800" - )} type="text" ref={searchField} value={searchString} onChange={handleSearchChange} onKeyDown={handleSearchKeyDown} /> + )} type="text" autoCorrect="false" ref={searchField} value={searchString} onChange={handleSearchChange} onKeyDown={handleSearchKeyDown} /> {results.length > 0 &&
        {results.map((r, i) => (
      • @@ -112,7 +121,7 @@ export default function QuickLaunch({servicesAndBookmarks, searchString, setSear
      • ))}
      } -
      +
      diff --git a/src/pages/index.jsx b/src/pages/index.jsx index 400d7eb5..bf64cc59 100644 --- a/src/pages/index.jsx +++ b/src/pages/index.jsx @@ -209,7 +209,7 @@ function Home({ initialSettings }) { document.addEventListener('keydown', handleKeyDown); return function cleanup() { - document.removeEventListener('keydown', handleKeyDown); + document.removeEventListener('keydown', handleKeyDown); } }) From f62021633b601475f461550054e49deaa8478e6b Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Wed, 19 Oct 2022 23:07:01 -0700 Subject: [PATCH 10/12] Respect settings target and allow command override --- src/components/quicklaunch.jsx | 18 +++++++++++------- src/pages/index.jsx | 2 +- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/components/quicklaunch.jsx b/src/components/quicklaunch.jsx index d40e50d0..a78ed598 100644 --- a/src/components/quicklaunch.jsx +++ b/src/components/quicklaunch.jsx @@ -1,28 +1,32 @@ import { useTranslation } from "react-i18next"; -import { useEffect, useState, useRef, useCallback } from "react"; +import { useEffect, useState, useRef, useCallback, useContext } from "react"; import classNames from "classnames"; import { resolveIcon } from "./services/item"; +import { SettingsContext } from "utils/contexts/settings"; + export default function QuickLaunch({servicesAndBookmarks, searchString, setSearchString, isOpen, close}) { const { t } = useTranslation(); + const { settings } = useContext(SettingsContext); const searchField = useRef(); const [results, setResults] = useState([]); const [currentItemIndex, setCurrentItemIndex] = useState(null); - function openCurrentItem() { + function openCurrentItem(newWindow) { const result = results[currentItemIndex]; - window.open(result.href, '_blank'); + window.open(result.href, newWindow ? "_blank" : settings.target ?? "_blank"); } const closeAndReset = useCallback(() => { close(false); setTimeout(() => { setSearchString(""); + setCurrentItemIndex(null); }, 200); // delay a little for animations - }, [close, setSearchString]); + }, [close, setSearchString, setCurrentItemIndex]); function handleSearchChange(event) { setSearchString(event.target.value.toLowerCase()) @@ -33,7 +37,7 @@ export default function QuickLaunch({servicesAndBookmarks, searchString, setSear closeAndReset(); } else if (event.key === "Enter" && results.length) { closeAndReset(); - openCurrentItem(); + openCurrentItem(event.metaKey); } else if (event.key === "ArrowDown" && results[currentItemIndex + 1]) { setCurrentItemIndex(currentItemIndex + 1); event.preventDefault(); @@ -47,9 +51,9 @@ export default function QuickLaunch({servicesAndBookmarks, searchString, setSear setCurrentItemIndex(parseInt(event.target?.dataset?.index, 10)); } - function handleItemClick() { + function handleItemClick(event) { closeAndReset(); - openCurrentItem(); + openCurrentItem(event.metaKey); } useEffect(() => { diff --git a/src/pages/index.jsx b/src/pages/index.jsx index bf64cc59..7a0fdb08 100644 --- a/src/pages/index.jsx +++ b/src/pages/index.jsx @@ -197,7 +197,7 @@ function Home({ initialSettings }) { useEffect(() => { function handleKeyDown(e) { if (e.target.tagName === "BODY") { - if (String.fromCharCode(e.keyCode).match(/(\w|\s)/g) && !(e. altKey || e.ctrlKey || e.metaKey || e.shiftKey)) { + 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(""); From 9c1c0e44655c393ec6b448f71558a93e44270afe Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Thu, 20 Oct 2022 11:12:47 -0700 Subject: [PATCH 11/12] Add quicklook searchDescriptions option --- src/components/quicklaunch.jsx | 31 +++++++++++++++++++++++++++---- src/pages/index.jsx | 9 ++++++++- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/src/components/quicklaunch.jsx b/src/components/quicklaunch.jsx index a78ed598..4d99bb59 100644 --- a/src/components/quicklaunch.jsx +++ b/src/components/quicklaunch.jsx @@ -6,7 +6,7 @@ import { resolveIcon } from "./services/item"; import { SettingsContext } from "utils/contexts/settings"; -export default function QuickLaunch({servicesAndBookmarks, searchString, setSearchString, isOpen, close}) { +export default function QuickLaunch({servicesAndBookmarks, searchString, setSearchString, isOpen, close, searchDescriptions}) { const { t } = useTranslation(); const { settings } = useContext(SettingsContext); @@ -59,13 +59,27 @@ export default function QuickLaunch({servicesAndBookmarks, searchString, setSear useEffect(() => { if (searchString.length === 0) setResults([]); else { - const newResults = servicesAndBookmarks.filter(r => r.name.toLowerCase().includes(searchString)); + let newResults = servicesAndBookmarks.filter(r => { + const nameMatch = r.name.toLowerCase().includes(searchString); + let descriptionMatch; + if (searchDescriptions) { + descriptionMatch = r.description?.toLowerCase().includes(searchString) + r.priority = nameMatch ? 2 * (+nameMatch) : +descriptionMatch; // eslint-disable-line no-param-reassign + } + return nameMatch || descriptionMatch; + }); + + if (searchDescriptions) { + newResults = newResults.sort((a, b) => b.priority - a.priority); + } + setResults(newResults); + if (newResults.length) { setCurrentItemIndex(0); } } - }, [searchString, servicesAndBookmarks]); + }, [searchString, servicesAndBookmarks, searchDescriptions]); const [hidden, setHidden] = useState(true); @@ -87,6 +101,11 @@ export default function QuickLaunch({servicesAndBookmarks, searchString, setSear }, [isOpen, closeAndReset]); + function highlightText(text) { + const parts = text.split(new RegExp(`(${searchString})`, 'gi')); + return {parts.map(part => part.toLowerCase() === searchString.toLowerCase() ? {part} : part)}; + } + return (
      {r.name} - {r.description && {r.description}} + {r.description && + + {searchDescriptions && r.priority < 2 ? highlightText(r.description) : r.description} + + }
      {r.abbr ? t("homepagesearch.bookmark") : t("homepagesearch.service")}
      diff --git a/src/pages/index.jsx b/src/pages/index.jsx index 7a0fdb08..a180c642 100644 --- a/src/pages/index.jsx +++ b/src/pages/index.jsx @@ -236,7 +236,14 @@ function Home({ initialSettings }) { headerStyles[initialSettings.headerStyle || "underlined"] )} > - + {widgets && ( <> {widgets From 689e2a80117f9b8e6372b6ce6e4f7de4e463b2b2 Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Thu, 20 Oct 2022 11:35:21 -0700 Subject: [PATCH 12/12] Quicklook support individual item target --- src/components/quicklaunch.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/quicklaunch.jsx b/src/components/quicklaunch.jsx index 4d99bb59..58d15cef 100644 --- a/src/components/quicklaunch.jsx +++ b/src/components/quicklaunch.jsx @@ -17,7 +17,7 @@ export default function QuickLaunch({servicesAndBookmarks, searchString, setSear function openCurrentItem(newWindow) { const result = results[currentItemIndex]; - window.open(result.href, newWindow ? "_blank" : settings.target ?? "_blank"); + window.open(result.href, newWindow ? "_blank" : result.target ?? settings.target ?? "_blank"); } const closeAndReset = useCallback(() => {