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:
parent
a136c0ab9b
commit
a9cf5630fe
@ -1,16 +1,19 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import SuggestionBox from "./suggestionBox";
|
import SuggestionBox from "./suggestionBox";
|
||||||
import Suggestion from "./suggestion";
|
|
||||||
import { useRecoilValue } from "recoil";
|
import { useRecoilValue } from "recoil";
|
||||||
import { queryState } from "@/components/state/query";
|
import { queryState } from "@/components/state/query";
|
||||||
import { Suggestion as SuggestionType } from "search-engine-autocomplete";
|
|
||||||
import { useLocale, useTranslations } from "next-intl";
|
import { useLocale, useTranslations } from "next-intl";
|
||||||
import { suggestionItem, suggestionsResponse } from "@/global";
|
import { suggestionItem, suggestionsResponse } from "@/global";
|
||||||
import getSearchEngineName from "@/lib/getSearchEngineName";
|
import getSearchEngineName from "@/lib/getSearchEngineName";
|
||||||
|
import PlainSearch from "./plainSearch";
|
||||||
|
|
||||||
export default function () {
|
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 [lastUpdate, setLastUpdate] = useState(0);
|
||||||
|
const [cloudLastUpdate, setCloudLastUpdate] = useState(0);
|
||||||
|
const [clientLastUpdate, setClientLastUpdate] = useState(0);
|
||||||
|
const devMode = true;
|
||||||
const query = useRecoilValue(queryState);
|
const query = useRecoilValue(queryState);
|
||||||
const engineName = getSearchEngineName();
|
const engineName = getSearchEngineName();
|
||||||
const lang = useLocale();
|
const lang = useLocale();
|
||||||
@ -18,18 +21,118 @@ export default function () {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const time = new Date().getTime().toString();
|
const time = new Date().getTime().toString();
|
||||||
|
if (query.trim() === "") {
|
||||||
|
cleanSuggestion("QUERY", new Date().getTime());
|
||||||
|
return;
|
||||||
|
}
|
||||||
fetch(`/api/suggestion?q=${query}&l=${lang}&t=${time}`)
|
fetch(`/api/suggestion?q=${query}&l=${lang}&t=${time}`)
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((data: suggestionsResponse) => {
|
.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(() => {
|
useEffect(() => {
|
||||||
setLastUpdate(new Date().getTime());
|
updateSuggestion(
|
||||||
}, [query]);
|
[
|
||||||
|
{
|
||||||
|
type: "default",
|
||||||
|
suggestion: query,
|
||||||
|
relevance: 2000
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"frontend"
|
||||||
|
);
|
||||||
|
}, [query, engineName]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SuggestionBox>
|
<SuggestionBox>
|
||||||
|
{suggestion.map((s) => {
|
||||||
|
if (s.suggestion.trim() === "") return;
|
||||||
|
if (s.type === "default") {
|
||||||
|
return (
|
||||||
|
<PlainSearch key={s.suggestion} query={s.suggestion}>
|
||||||
|
{s.suggestion}
|
||||||
|
<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">
|
||||||
|
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">
|
||||||
|
Relevance: {s.relevance}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</PlainSearch>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})}
|
||||||
</SuggestionBox>
|
</SuggestionBox>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
18
components/search/onesearch/plainSearch.tsx
Normal file
18
components/search/onesearch/plainSearch.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
@ -1,35 +1,50 @@
|
|||||||
|
import { settingsType } from "@/global";
|
||||||
import isLocalStorageAvailable from "@/lib/isLocalStorageAvailable";
|
import isLocalStorageAvailable from "@/lib/isLocalStorageAvailable";
|
||||||
import { atom } from "recoil";
|
import { atom } from "recoil";
|
||||||
|
|
||||||
const defaultSettings: settings = {
|
const defaultSettings: settingsType = {
|
||||||
version: 1,
|
"version": 2,
|
||||||
elementBackdrop: true,
|
"elementBackdrop": true,
|
||||||
bgBlur: true,
|
"bgBlur": true,
|
||||||
timeShowSecond: false,
|
"timeShowSecond": false,
|
||||||
currentSearchEngine: "google",
|
"currentSearchEngine": "google",
|
||||||
searchEngines: {
|
"searchInNewTab": true,
|
||||||
google: "https://www.google.com/search?q=%s",
|
"searchEngines": {
|
||||||
bing: "https://www.bing.com/search?q=%s",
|
"google": "https://www.google.com/search?q=%s",
|
||||||
baidu: "https://www.baidu.com/s?wd=%s",
|
"bing": "https://www.bing.com/search?q=%s",
|
||||||
duckduckgo: "https://duckduckgo.com/?q=%s",
|
"baidu": "https://www.baidu.com/s?wd=%s",
|
||||||
yandex: "https://yandex.com/search/?text=%s",
|
"duckduckgo": "https://duckduckgo.com/?q=%s",
|
||||||
yahoo: "https://search.yahoo.com/search?p=%s",
|
"yandex": "https://yandex.com/search/?text=%s",
|
||||||
ecosia: "https://www.ecosia.org/search?q=%s"
|
"yahoo": "https://search.yahoo.com/search?p=%s",
|
||||||
|
"ecosia": "https://www.ecosia.org/search?q=%s"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const localStorageEffect =
|
const localStorageEffect =
|
||||||
(key: any) =>
|
(key: string) =>
|
||||||
({ setSelf, onSet }: any) => {
|
({ setSelf, onSet }: any) => {
|
||||||
if (isLocalStorageAvailable()===false){
|
if (isLocalStorageAvailable()===false){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const savedValue = localStorage.getItem(key);
|
if (localStorage.getItem(key) === null) {
|
||||||
if (savedValue != null) {
|
localStorage.setItem(key, JSON.stringify(defaultSettings));
|
||||||
setSelf(JSON.parse(savedValue));
|
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));
|
localStorage.setItem(key, JSON.stringify(newValue));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
25
global.d.ts
vendored
25
global.d.ts
vendored
@ -1,17 +1,18 @@
|
|||||||
import { Suggestion } from "search-engine-autocomplete";
|
import { Suggestion } from "search-engine-autocomplete";
|
||||||
|
|
||||||
type settingsType = {
|
interface settingsType extends Object{
|
||||||
version: number;
|
"version": number,
|
||||||
elementBackdrop: boolean;
|
"elementBackdrop": boolean,
|
||||||
bgBlur: boolean;
|
"bgBlur": boolean,
|
||||||
timeShowSecond: boolean;
|
"timeShowSecond": boolean,
|
||||||
currentSearchEngine: string;
|
"currentSearchEngine": string,
|
||||||
searchEngines: {
|
"searchInNewTab": boolean,
|
||||||
[key: string]: string,
|
"searchEngines": {
|
||||||
};
|
[key: string]: string
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
type suggestionsResponse = {
|
interface suggestionsResponse extends Object{
|
||||||
suggestions: Suggestion[],
|
suggestions: Suggestion[],
|
||||||
query: string,
|
query: string,
|
||||||
verbatimRelevance: number,
|
verbatimRelevance: number,
|
||||||
@ -21,6 +22,6 @@ type suggestionsResponse = {
|
|||||||
type suggestionItem = {
|
type suggestionItem = {
|
||||||
suggestion: string,
|
suggestion: string,
|
||||||
type: string,
|
type: string,
|
||||||
relativeRelevance: number,
|
relativeRelevance?: number,
|
||||||
relevance?: number
|
relevance: number
|
||||||
}
|
}
|
4
lib/search.ts
Normal file
4
lib/search.ts
Normal 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);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user