From 136450f93d91e563f68d27e7af4d3aafaaa326e6 Mon Sep 17 00:00:00 2001 From: Alikia2x Date: Fri, 26 Apr 2024 18:10:31 +0800 Subject: [PATCH] feature: base structure of onesearch, no bugs now --- components/search/onesearch/handleEnter.ts | 14 ++ components/search/onesearch/link.tsx | 30 ++++ components/search/onesearch/onesearch.tsx | 154 +++++++++++--------- components/search/onesearch/plainSearch.tsx | 37 +++-- components/search/search.tsx | 29 ++-- components/state/suggestion.ts | 11 ++ components/state/suggestionSelection.ts | 10 ++ lib/normalizeURL.ts | 1 - 8 files changed, 197 insertions(+), 89 deletions(-) create mode 100644 components/search/onesearch/handleEnter.ts create mode 100644 components/search/onesearch/link.tsx create mode 100644 components/state/suggestion.ts create mode 100644 components/state/suggestionSelection.ts diff --git a/components/search/onesearch/handleEnter.ts b/components/search/onesearch/handleEnter.ts new file mode 100644 index 0000000..fdb8ca0 --- /dev/null +++ b/components/search/onesearch/handleEnter.ts @@ -0,0 +1,14 @@ +import { settingsType, suggestionItem } from "@/global"; +import { normalizeURL } from "@/lib/normalizeURL"; +import search from "@/lib/search"; + +export default function (index: number, suggestion: suggestionItem[], query: string, settings: settingsType) { + const selected = suggestion[index]; + const engine = settings.searchEngines[settings.currentSearchEngine]; + const newTab = settings.searchInNewTab; + if (selected.type === "QUERY" || selected.type === "default") { + search(selected.suggestion, engine, newTab); + } else if (selected.type === "NAVIGATION") { + window.open(normalizeURL(selected.suggestion)); + } +} diff --git a/components/search/onesearch/link.tsx b/components/search/onesearch/link.tsx new file mode 100644 index 0000000..6cbbaf8 --- /dev/null +++ b/components/search/onesearch/link.tsx @@ -0,0 +1,30 @@ +import { normalizeURL } from "@/lib/normalizeURL"; + +export default function (props: { children: React.ReactNode; query: string; selected: boolean }) { + if (props.selected) { + return ( +
{ + window.open(normalizeURL(props.query)); + }} + > + {props.children} +
+ ); + } + else { + return ( +
{ + window.open(normalizeURL(props.query)); + }} + > + {props.children} +
+ ); + } +} diff --git a/components/search/onesearch/onesearch.tsx b/components/search/onesearch/onesearch.tsx index 8a24671..ddafdda 100644 --- a/components/search/onesearch/onesearch.tsx +++ b/components/search/onesearch/onesearch.tsx @@ -1,114 +1,108 @@ -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import SuggestionBox from "./suggestionBox"; -import { useRecoilValue } from "recoil"; +import { useRecoilState, useRecoilValue } from "recoil"; import { queryState } from "@/components/state/query"; import { useLocale, useTranslations } from "next-intl"; import { suggestionItem, suggestionsResponse } from "@/global"; import getSearchEngineName from "@/lib/getSearchEngineName"; import PlainSearch from "./plainSearch"; +import { suggestionsState } from "@/components/state/suggestion"; +import validLink from "@/lib/url/validLink"; +import Link from "./link"; +import { selectedSuggestionState } from "@/components/state/suggestionSelection"; +import { settingsState } from "@/components/state/settings"; +import { base64NLP } from "@/lib/onesearch/baseCheck"; +import PlainText from "./plainText"; export default function () { - const [suggestion, setFinalSuggetsion] = useState([] as suggestionItem[]); - const [selected, setSelected] = useState(0); - const [lastUpdate, setLastUpdate] = useState(0); - const [cloudLastUpdate, setCloudLastUpdate] = useState(0); - const [clientLastUpdate, setClientLastUpdate] = useState(0); + const [suggestion, setFinalSuggetsion] = useRecoilState(suggestionsState); + const lastRequestTimeRef = useRef(0); + const selected = useRecoilValue(selectedSuggestionState); + const settings = useRecoilValue(settingsState); const devMode = true; const query = useRecoilValue(queryState); const engineName = getSearchEngineName(); + const engine = settings.currentSearchEngine; const lang = useLocale(); const t = useTranslations("Search"); useEffect(() => { const time = new Date().getTime().toString(); if (query.trim() === "") { - cleanSuggestion("QUERY", new Date().getTime()); + cleanSuggestion("QUERY", "NAVIGATION"); return; } - fetch(`/api/suggestion?q=${query}&l=${lang}&t=${time}`) + fetch(`/api/suggestion?q=${query}&l=${lang}&t=${time}&engine=${engine}`) .then((res) => res.json()) .then((data: suggestionsResponse) => { let suggestionToUpdate: suggestionItem[] = data.suggestions; - if (data.verbatimRelevance) { - suggestionToUpdate.push({ - type: "default", - suggestion: query, - relevance: data.verbatimRelevance - }); + if (data.time > lastRequestTimeRef.current){ + cleanSuggestion("NAVIGATION", "QUERY"); + lastRequestTimeRef.current = data.time; + updateSuggestion(suggestionToUpdate); } - updateSuggestion(suggestionToUpdate, "cloud", data.time); + }); }, [query]); - function setSuggestion(data: suggestionItem[], source: "frontend" | "client" | "cloud", time?: number) { - let finalSuggestion = data.sort((a, b) => b.relevance - a.relevance); - finalSuggestion = finalSuggestion.filter( - (item, index, self) => index === self.findIndex((t) => t.suggestion === item.suggestion) - ); - setFinalSuggetsion(finalSuggestion); - const requestUpdateTime = time || new Date().getTime(); - const overwriteLastUpdate = requestUpdateTime > lastUpdate; - switch (source) { - case "frontend": - setLastUpdate(new Date().getTime()); - break; - case "client": - setClientLastUpdate(requestUpdateTime); - if (overwriteLastUpdate) setLastUpdate(new Date().getTime()); - case "cloud": - setCloudLastUpdate(requestUpdateTime); - if (overwriteLastUpdate) setLastUpdate(new Date().getTime()); - break; - default: - break; - } - } - - function updateSuggestion(data: suggestionItem[], source: "frontend" | "client" | "cloud", time?: number) { - let finalSuggestion = suggestion; - const types: string[] = []; - for (let sug of data) { - if (!types.includes(sug.type)) types.push(sug.type); - } - for (let type of types) { - finalSuggestion = finalSuggestion.filter((item) => { - return item.type !== type; + function updateSuggestion(data: suggestionItem[]) { + setFinalSuggetsion((cur: suggestionItem[]) => { + const types: string[] = []; + for (let sug of data) { + if (!types.includes(sug.type)) types.push(sug.type); + } + for (let type of types) { + cur = cur.filter((item) => { + return item.type !== type; + }); + } + return cur.concat(data).sort((a, b) => { + return b.relevance - a.relevance; }); - } - finalSuggestion = finalSuggestion.concat(data); - setSuggestion(finalSuggestion, source, time); + }); } - function cleanSuggestion(type: string, time?: number){ - let finalSuggestion = suggestion; - console.log("1",finalSuggestion); - finalSuggestion = finalSuggestion.filter((item) => { - return item.type !== type; + function cleanSuggestion(...types: string[]) { + setFinalSuggetsion((suggestion: suggestionItem[]) => { + return suggestion.filter((item) => { + return !types.includes(item.type); + }); }); - console.log("2",finalSuggestion); - setSuggestion(finalSuggestion, "frontend", time); } useEffect(() => { - updateSuggestion( - [ + if (validLink(query)) { + cleanSuggestion("default-link","default","text") + updateSuggestion([ + { type: "default-link", suggestion: query, relevance: 3000 }, + { type: "default", suggestion: query, relevance: 1600 } + ]); + } else { + cleanSuggestion("default-link","default","text") + updateSuggestion([ { type: "default", suggestion: query, relevance: 2000 } - ], - "frontend" - ); + ]); + } + console.log(new Date().getTime()); + const b64 = base64NLP(query); + console.log(new Date().getTime()); + if (b64.suggestion!==null){ + cleanSuggestion("default-link","default","text") + updateSuggestion([b64 as suggestionItem]); + } }, [query, engineName]); return ( - {suggestion.map((s) => { + {suggestion.map((s, i) => { if (s.suggestion.trim() === "") return; if (s.type === "default") { return ( - + {s.suggestion}  {t("search-help-text", { engine: engineName })} @@ -122,7 +116,7 @@ export default function () { ); } else if (s.type === "QUERY") { return ( - + {s.suggestion} {devMode && ( @@ -131,6 +125,32 @@ export default function () { )} ); + } else if (s.type === "NAVIGATION" || s.type === "default-link") { + return ( + + {s.suggestion} + {devMode && ( + +   Relevance: {s.relevance} + + )} + + ); + } else if (s.type === "text") { + return ( + + <p>{s.suggestion}</p> + {devMode && ( + <span className="text-zinc-700 dark:text-zinc-400 text-sm"> + Relevance: {s.relevance} + </span> + )} + </PlainText> + ) } })} </SuggestionBox> diff --git a/components/search/onesearch/plainSearch.tsx b/components/search/onesearch/plainSearch.tsx index b545a43..c8deb7e 100644 --- a/components/search/onesearch/plainSearch.tsx +++ b/components/search/onesearch/plainSearch.tsx @@ -2,17 +2,34 @@ import search from "@/lib/search"; import { settingsState } from "@/components/state/settings"; import { useRecoilValue } from "recoil"; -export default function (props: { children: React.ReactNode, query: string }) { +export default function (props: { children: React.ReactNode; query: string; selected: boolean }) { const settings = useRecoilValue(settingsState); const engine = settings.searchEngines[settings.currentSearchEngine]; const newTab = settings.searchInNewTab; - return ( - <div - className={`w-full h-10 leading-10 bg-zinc-100 hover:bg-zinc-300 - dark:bg-zinc-800 hover:dark:bg-zinc-700 px-5 z-10 cursor-pointer duration-100`} - onClick={() => {search(props.query, engine, newTab)}} - > - {props.children} - </div> - ); + if (props.selected) { + return ( + <div + className={`w-full h-10 leading-10 bg-zinc-300 dark:bg-zinc-700 + px-5 z-10 cursor-pointer duration-100`} + onClick={() => { + search(props.query, engine, newTab); + }} + > + {props.children} + </div> + ); + } + else { + return ( + <div + className={`w-full h-10 leading-10 bg-zinc-100 hover:bg-zinc-300 + dark:bg-zinc-800 hover:dark:bg-zinc-700 px-5 z-10 cursor-pointer duration-100`} + onClick={() => { + search(props.query, engine, newTab); + }} + > + {props.children} + </div> + ); + } } diff --git a/components/search/search.tsx b/components/search/search.tsx index 20064ff..8fac048 100644 --- a/components/search/search.tsx +++ b/components/search/search.tsx @@ -3,28 +3,35 @@ import { useRecoilState, useRecoilValue } from "recoil"; import { settingsState } from "../state/settings"; import { useTranslations } from "next-intl"; -import { normalizeURL } from "@/lib/normalizeURL"; -import validLink from "@/lib/url/validLink"; import { queryState } from "../state/query"; import { settingsType } from "@/global"; +import handleEnter from "./onesearch/handleEnter"; +import { selectedSuggestionState } from "../state/suggestionSelection"; +import { suggestionsState } from "../state/suggestion"; +import { KeyboardEvent } from "react"; export default function Search(props: { onFocus: () => void }) { const settings: settingsType = useRecoilValue(settingsState); const t = useTranslations("Search"); const [query, setQuery] = useRecoilState(queryState); + const [selectedSuggestion, setSelected] = useRecoilState(selectedSuggestionState); + const suggestions = useRecoilValue(suggestionsState); let style = "default"; - function handleKeydown(e: any) { - let URL = ""; - if (validLink(query)) { - URL = normalizeURL(query); - } else { - URL = settings.searchEngines[settings.currentSearchEngine]; - URL = URL.replace("%s", query); - } + function handleKeydown(e: KeyboardEvent) { if (e.key == "Enter") { - location.href = URL; + e.preventDefault(); + handleEnter(selectedSuggestion, suggestions, query, settings); + return; + } else if (e.key == "ArrowUp") { + e.preventDefault(); + const len = suggestions.length; + setSelected((selectedSuggestion - 1 + len) % len); + } else if (e.key == "ArrowDown") { + e.preventDefault(); + const len = suggestions.length; + setSelected((selectedSuggestion + 1) % len); } } diff --git a/components/state/suggestion.ts b/components/state/suggestion.ts new file mode 100644 index 0000000..4dc83ef --- /dev/null +++ b/components/state/suggestion.ts @@ -0,0 +1,11 @@ +import { suggestionItem } from "@/global"; +import { atom } from "recoil"; + +const suggestionsState = atom({ + key: "oneSearchSuggestions", + default: [] as suggestionItem[] +}); + +export { + suggestionsState, +} \ No newline at end of file diff --git a/components/state/suggestionSelection.ts b/components/state/suggestionSelection.ts new file mode 100644 index 0000000..4142142 --- /dev/null +++ b/components/state/suggestionSelection.ts @@ -0,0 +1,10 @@ +import { atom } from "recoil"; + +const selectedSuggestionState = atom({ + key: "selectedSuggestion", + default: 0 +}); + +export { + selectedSuggestionState, +} \ No newline at end of file diff --git a/lib/normalizeURL.ts b/lib/normalizeURL.ts index 998b3b9..e6aa831 100644 --- a/lib/normalizeURL.ts +++ b/lib/normalizeURL.ts @@ -12,7 +12,6 @@ export function normalizeURL(input: string): string { return urlWithHTTP.href; } catch (error) { // if the URL is still invalid, return the original input - console.error("Invalid URL:", input); return input; } }