feature: base structure of onesearch, no bugs now
This commit is contained in:
parent
a9cf5630fe
commit
136450f93d
14
components/search/onesearch/handleEnter.ts
Normal file
14
components/search/onesearch/handleEnter.ts
Normal 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));
|
||||
}
|
||||
}
|
30
components/search/onesearch/link.tsx
Normal file
30
components/search/onesearch/link.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
@ -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}
|
||||
<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">
|
||||
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>
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
11
components/state/suggestion.ts
Normal file
11
components/state/suggestion.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { suggestionItem } from "@/global";
|
||||
import { atom } from "recoil";
|
||||
|
||||
const suggestionsState = atom({
|
||||
key: "oneSearchSuggestions",
|
||||
default: [] as suggestionItem[]
|
||||
});
|
||||
|
||||
export {
|
||||
suggestionsState,
|
||||
}
|
10
components/state/suggestionSelection.ts
Normal file
10
components/state/suggestionSelection.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { atom } from "recoil";
|
||||
|
||||
const selectedSuggestionState = atom({
|
||||
key: "selectedSuggestion",
|
||||
default: 0
|
||||
});
|
||||
|
||||
export {
|
||||
selectedSuggestionState,
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user