feature: base structure of onesearch, no bugs now

This commit is contained in:
Alikia2x 2024-04-26 18:10:31 +08:00
parent a9cf5630fe
commit 136450f93d
8 changed files with 197 additions and 89 deletions

View File

@ -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));
}
}

View File

@ -0,0 +1,30 @@
import { normalizeURL } from "@/lib/normalizeURL";
export default function (props: { children: React.ReactNode; query: string; selected: boolean }) {
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={() => {
window.open(normalizeURL(props.query));
}}
>
{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={() => {
window.open(normalizeURL(props.query));
}}
>
{props.children}
</div>
);
}
}

View File

@ -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;
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) {
finalSuggestion = finalSuggestion.filter((item) => {
cur = cur.filter((item) => {
return item.type !== type;
});
}
finalSuggestion = finalSuggestion.concat(data);
setSuggestion(finalSuggestion, source, time);
return cur.concat(data).sort((a, b) => {
return b.relevance - a.relevance;
});
});
}
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 (
<SuggestionBox>
{suggestion.map((s) => {
{suggestion.map((s, i) => {
if (s.suggestion.trim() === "") return;
if (s.type === "default") {
return (
<PlainSearch key={s.suggestion} query={s.suggestion}>
<PlainSearch key={i} query={s.suggestion} selected={i == selected}>
{s.suggestion}&nbsp;
<span className="text-zinc-700 dark:text-zinc-400 text-sm">
{t("search-help-text", { engine: engineName })}
@ -122,7 +116,7 @@ export default function () {
);
} else if (s.type === "QUERY") {
return (
<PlainSearch key={s.suggestion} query={s.suggestion}>
<PlainSearch key={i} query={s.suggestion} selected={i == selected}>
{s.suggestion}
{devMode && (
<span className="text-zinc-700 dark:text-zinc-400 text-sm">
@ -131,6 +125,32 @@ export default function () {
)}
</PlainSearch>
);
} else if (s.type === "NAVIGATION" || s.type === "default-link") {
return (
<Link
key={i}
query={s.suggestion}
selected={i == selected}
>
{s.suggestion}
{devMode && (
<span className="text-zinc-700 dark:text-zinc-400 text-sm">
&nbsp;&nbsp;Relevance: {s.relevance}
</span>
)}
</Link>
);
} else if (s.type === "text") {
return (
<PlainText key={i} selected={i == selected}>
<p>{s.suggestion}</p>
{devMode && (
<span className="text-zinc-700 dark:text-zinc-400 text-sm">
Relevance: {s.relevance}
</span>
)}
</PlainText>
)
}
})}
</SuggestionBox>

View File

@ -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;
if (props.selected) {
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)}}
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>
);
}
}

View File

@ -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);
}
}

View File

@ -0,0 +1,11 @@
import { suggestionItem } from "@/global";
import { atom } from "recoil";
const suggestionsState = atom({
key: "oneSearchSuggestions",
default: [] as suggestionItem[]
});
export {
suggestionsState,
}

View File

@ -0,0 +1,10 @@
import { atom } from "recoil";
const selectedSuggestionState = atom({
key: "selectedSuggestion",
default: 0
});
export {
selectedSuggestionState,
}

View File

@ -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;
}
}