feature: complete function and code logic for suggestion (include cloud)

todo: problem: In a short period of time, several functions that depend on the latest state at the same time do operations and update the state according to the state they got, which will cause all calls except the first one to update the state incorrectly due to incorrect dependencies (not-updated state) because of the asynchronous updating of the React state.
This commit is contained in:
Alikia2x 2024-04-17 23:44:28 +08:00
parent a136c0ab9b
commit a9cf5630fe
5 changed files with 178 additions and 37 deletions

View File

@ -1,16 +1,19 @@
import { useEffect, useState } from "react";
import SuggestionBox from "./suggestionBox";
import Suggestion from "./suggestion";
import { useRecoilValue } from "recoil";
import { queryState } from "@/components/state/query";
import { Suggestion as SuggestionType } from "search-engine-autocomplete";
import { useLocale, useTranslations } from "next-intl";
import { suggestionItem, suggestionsResponse } from "@/global";
import getSearchEngineName from "@/lib/getSearchEngineName";
import PlainSearch from "./plainSearch";
export default function () {
const [suggestion, setSuggetsion] = useState([] as suggestionItem[]);
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 devMode = true;
const query = useRecoilValue(queryState);
const engineName = getSearchEngineName();
const lang = useLocale();
@ -18,18 +21,118 @@ export default function () {
useEffect(() => {
const time = new Date().getTime().toString();
if (query.trim() === "") {
cleanSuggestion("QUERY", new Date().getTime());
return;
}
fetch(`/api/suggestion?q=${query}&l=${lang}&t=${time}`)
.then((res) => res.json())
.then((data: suggestionsResponse) => {
let suggestionToUpdate: suggestionItem[] = data.suggestions;
if (data.verbatimRelevance) {
suggestionToUpdate.push({
type: "default",
suggestion: query,
relevance: data.verbatimRelevance
});
}
updateSuggestion(suggestionToUpdate, "cloud", data.time);
});
}, [query, engineName]);
}, [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;
});
}
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;
});
console.log("2",finalSuggestion);
setSuggestion(finalSuggestion, "frontend", time);
}
useEffect(() => {
setLastUpdate(new Date().getTime());
}, [query]);
updateSuggestion(
[
{
type: "default",
suggestion: query,
relevance: 2000
}
],
"frontend"
);
}, [query, engineName]);
return (
<SuggestionBox>
{suggestion.map((s) => {
if (s.suggestion.trim() === "") return;
if (s.type === "default") {
return (
<PlainSearch key={s.suggestion} query={s.suggestion}>
{s.suggestion}&nbsp;
<span className="text-zinc-700 dark:text-zinc-400 text-sm">
{t("search-help-text", { engine: engineName })}
</span>
{devMode && (
<span className="text-zinc-700 dark:text-zinc-400 text-sm">
&nbsp;&nbsp;Relevance: {s.relevance}
</span>
)}
</PlainSearch>
);
} else if (s.type === "QUERY") {
return (
<PlainSearch key={s.suggestion} query={s.suggestion}>
{s.suggestion}
{devMode && (
<span className="text-zinc-700 dark:text-zinc-400 text-sm">
&nbsp;&nbsp;Relevance: {s.relevance}
</span>
)}
</PlainSearch>
);
}
})}
</SuggestionBox>
);
}

View File

@ -0,0 +1,18 @@
import search from "@/lib/search";
import { settingsState } from "@/components/state/settings";
import { useRecoilValue } from "recoil";
export default function (props: { children: React.ReactNode, query: string }) {
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>
);
}

View File

@ -1,35 +1,50 @@
import { settingsType } from "@/global";
import isLocalStorageAvailable from "@/lib/isLocalStorageAvailable";
import { atom } from "recoil";
const defaultSettings: settings = {
version: 1,
elementBackdrop: true,
bgBlur: true,
timeShowSecond: false,
currentSearchEngine: "google",
searchEngines: {
google: "https://www.google.com/search?q=%s",
bing: "https://www.bing.com/search?q=%s",
baidu: "https://www.baidu.com/s?wd=%s",
duckduckgo: "https://duckduckgo.com/?q=%s",
yandex: "https://yandex.com/search/?text=%s",
yahoo: "https://search.yahoo.com/search?p=%s",
ecosia: "https://www.ecosia.org/search?q=%s"
const defaultSettings: settingsType = {
"version": 2,
"elementBackdrop": true,
"bgBlur": true,
"timeShowSecond": false,
"currentSearchEngine": "google",
"searchInNewTab": true,
"searchEngines": {
"google": "https://www.google.com/search?q=%s",
"bing": "https://www.bing.com/search?q=%s",
"baidu": "https://www.baidu.com/s?wd=%s",
"duckduckgo": "https://duckduckgo.com/?q=%s",
"yandex": "https://yandex.com/search/?text=%s",
"yahoo": "https://search.yahoo.com/search?p=%s",
"ecosia": "https://www.ecosia.org/search?q=%s"
}
};
const localStorageEffect =
(key: any) =>
(key: string) =>
({ setSelf, onSet }: any) => {
if (isLocalStorageAvailable()===false){
return;
}
const savedValue = localStorage.getItem(key);
if (savedValue != null) {
setSelf(JSON.parse(savedValue));
if (localStorage.getItem(key) === null) {
localStorage.setItem(key, JSON.stringify(defaultSettings));
return;
}
let settings =JSON.parse(JSON.stringify(defaultSettings));
const savedSettings = localStorage.getItem(key)!;
const parsedSettings = JSON.parse(savedSettings);
Object.keys(settings).map((key) => {
if (parsedSettings[key] !== undefined && key !== "version"){
settings[key] = parsedSettings[key];
}
})
onSet((newValue: settings) => {
setSelf(settings);
localStorage.setItem(key, JSON.stringify(settings));
onSet((newValue: settingsType) => {
localStorage.setItem(key, JSON.stringify(newValue));
});
};

25
global.d.ts vendored
View File

@ -1,17 +1,18 @@
import { Suggestion } from "search-engine-autocomplete";
type settingsType = {
version: number;
elementBackdrop: boolean;
bgBlur: boolean;
timeShowSecond: boolean;
currentSearchEngine: string;
searchEngines: {
[key: string]: string,
};
interface settingsType extends Object{
"version": number,
"elementBackdrop": boolean,
"bgBlur": boolean,
"timeShowSecond": boolean,
"currentSearchEngine": string,
"searchInNewTab": boolean,
"searchEngines": {
[key: string]: string
},
};
type suggestionsResponse = {
interface suggestionsResponse extends Object{
suggestions: Suggestion[],
query: string,
verbatimRelevance: number,
@ -21,6 +22,6 @@ type suggestionsResponse = {
type suggestionItem = {
suggestion: string,
type: string,
relativeRelevance: number,
relevance?: number
relativeRelevance?: number,
relevance: number
}

4
lib/search.ts Normal file
View File

@ -0,0 +1,4 @@
export default function(query: string, engine: string, newTab: boolean = true) {
if(newTab) window.open(engine.replace("%s", query));
else window.location.href = engine.replace("%s", query);
}