ref: use react router

fix: a critical performance issue
add: inpage-link for onesearch
This commit is contained in:
alikia2x 2024-08-04 03:26:33 +08:00
parent 263b82c06e
commit dee9dff8e8
12 changed files with 162 additions and 121 deletions

View File

@ -1,30 +1,30 @@
import { normalizeURL } from "lib/normalizeURL"; import { normalizeURL } from "lib/normalizeURL";
import { useNavigate } from "react-router";
export default function Link(props: { children: React.ReactNode; query: string; selected: boolean }) { interface LinkSuggestionProps {
if (props.selected) { children: React.ReactNode;
return ( query: string;
<div selected: boolean;
className={`w-full h-10 leading-10 bg-zinc-300 dark:bg-zinc-700 inPage?: boolean;
px-5 z-10 cursor-pointer duration-100`} }
onClick={() => {
window.open(normalizeURL(props.query)); export default function LinkSuggestion(props: LinkSuggestionProps) {
}} const className = props.selected
> ? `w-full h-10 leading-10 bg-zinc-300 dark:bg-zinc-700 px-5 z-10 cursor-pointer duration-100`
{props.children} : `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`;
</div> const navigate = useNavigate();
); return (
} <div
else { className={className}
return ( onClick={() => {
<div if (props.inPage) {
className={`w-full h-10 leading-10 bg-zinc-100 hover:bg-zinc-300 navigate(props.query);
dark:bg-zinc-800 hover:dark:bg-zinc-700 px-5 z-10 cursor-pointer duration-100`} } else {
onClick={() => { window.open(normalizeURL(props.query));
window.open(normalizeURL(props.query)); }
}} }}
> >
{props.children} {props.children}
</div> </div>
); );
}
} }

View File

@ -6,7 +6,7 @@ import getSearchEngineName from "lib/onesearch/getSearchEngineName";
import PlainSearch from "./plainSearch"; import PlainSearch from "./plainSearch";
import { suggestionAtom } from "lib/state/suggestion"; import { suggestionAtom } from "lib/state/suggestion";
import validLink from "lib/url/validLink"; import validLink from "lib/url/validLink";
import Link from "./link"; import LinkSuggestion from "./link";
import { selectedSuggestionAtom } from "lib/state/suggestionSelection"; import { selectedSuggestionAtom } from "lib/state/suggestionSelection";
import { settingsAtom } from "lib/state/settings"; import { settingsAtom } from "lib/state/settings";
import PlainText from "./plainText"; import PlainText from "./plainText";
@ -86,12 +86,14 @@ export default function OneSearch() {
}); });
} }
(async function () { useEffect(() => {
const NLU = await import("lib/nlp/load"); (async function () {
const mainNLUModel = new NLU.NLU(); const NLU = await import("lib/nlp/load");
setNLUModel(mainNLUModel); const mainNLUModel = new NLU.NLU();
setNLUModelLoaded(true); setNLUModel(mainNLUModel);
})(); setNLUModelLoaded(true);
})();
}, []);
useEffect(() => { useEffect(() => {
if (NLUModel === null || NLUModel === undefined) { if (NLUModel === null || NLUModel === undefined) {
@ -172,7 +174,7 @@ export default function OneSearch() {
s.type === "link" s.type === "link"
) { ) {
return ( return (
<Link key={i} query={s.suggestion} selected={i == selected}> <LinkSuggestion key={i} query={s.suggestion} selected={i == selected}>
{s.prompt && ( {s.prompt && (
<span className="text-zinc-700 dark:text-zinc-400">{s.prompt}</span> <span className="text-zinc-700 dark:text-zinc-400">{s.prompt}</span>
)} )}
@ -182,7 +184,7 @@ export default function OneSearch() {
{s.relevance} {s.relevance}
</span> </span>
)} )}
</Link> </LinkSuggestion>
); );
} else if (s.type === "text") { } else if (s.type === "text") {
return ( return (
@ -198,6 +200,25 @@ export default function OneSearch() {
)} )}
</PlainText> </PlainText>
); );
} else if (s.type === "inpage-link") {
return (
<LinkSuggestion
key={i}
query={s.suggestion}
selected={i == selected}
inPage={true}
>
{s.prompt && (
<span className="text-zinc-700 dark:text-zinc-400">{s.prompt}</span>
)}
{s.suggestion}
{devMode && (
<span className="absolute text-zinc-700 dark:text-zinc-400 text-sm leading-10 h-10 right-2">
{s.relevance}
</span>
)}
</LinkSuggestion>
);
} }
})} })}
</SuggestionBox> </SuggestionBox>

View File

@ -18,8 +18,11 @@ export default function (
} else if (selected.type === "NAVIGATION" || selected.type === "default-link") { } else if (selected.type === "NAVIGATION" || selected.type === "default-link") {
window.open(normalizeURL(selected.suggestion)); window.open(normalizeURL(selected.suggestion));
} else if (selected.type === "text") { } else if (selected.type === "text") {
console.log("????");
copyToClipboard(selected.suggestion); copyToClipboard(selected.suggestion);
searchBoxRef.current?.focus(); searchBoxRef.current?.focus();
} else if (selected.type === "link") {
window.open(normalizeURL(selected.suggestion));
} else if (selected.type === "inpage-link") {
location.href = normalizeURL(selected.suggestion);
} }
} }

View File

@ -16,7 +16,7 @@ export function keywordSuggestion(query: string) {
for (const keyword in dict_cn) { for (const keyword in dict_cn) {
if (query.includes(keyword)) { if (query.includes(keyword)) {
const result: suggestionItem = { const result: suggestionItem = {
type: "link", type: "inpage-link",
suggestion: dict_cn[keyword], suggestion: dict_cn[keyword],
prompt: keyword, prompt: keyword,
relevance: 3000 relevance: 3000
@ -27,7 +27,7 @@ export function keywordSuggestion(query: string) {
for (const keyword in dict_en) { for (const keyword in dict_en) {
if (query.includes(keyword)) { if (query.includes(keyword)) {
const result: suggestionItem = { const result: suggestionItem = {
type: "link", type: "inpage-link",
suggestion: dict_en[keyword], suggestion: dict_en[keyword],
prompt: keyword, prompt: keyword,
relevance: 3000 relevance: 3000

View File

@ -1,7 +1,7 @@
{ {
"name": "sparkhome", "name": "sparkhome",
"private": false, "private": false,
"version": "5.6.0", "version": "5.7.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "bun server.ts", "dev": "bun server.ts",

View File

@ -58,7 +58,7 @@ function Version(props: { title: string; version: string; versionClass?: string
<span <span
className={ className={
"relative px-2 py-1 text-sm font-bold rounded-md text-nowrap text-white " + "relative px-2 py-1 text-sm font-bold rounded-md text-nowrap text-white " +
props.versionClass ?? "" props.versionClass || ""
} }
> >
{props.version} {props.version}

View File

@ -12,8 +12,9 @@ export default function Homepage() {
const setBgFocus = useSetAtom(bgFocusAtom); const setBgFocus = useSetAtom(bgFocusAtom);
return ( return (
<div className="h-screen fixed overflow-hidden w-screen bg-black"> <div className="h-screen w-screen overflow-x-hidden bg-white dark:bg-[rgb(23,25,29)]">
<Background /> <Background />
<EngineSelector <EngineSelector
className="absolute top-20 lg:top-44 short:top-0 translate-x-[-50%] translate-y-[-0.2rem] className="absolute top-20 lg:top-44 short:top-0 translate-x-[-50%] translate-y-[-0.2rem]
left-1/2 w-11/12 sm:w-[700px] text:black text-right left-1/2 w-11/12 sm:w-[700px] text:black text-right

View File

@ -1,74 +1,31 @@
import { Suspense } from "react"; import { createBrowserRouter, RouterProvider } from "react-router-dom";
import { useRoutes } from "react-router-dom";
import routes from "~react-pages";
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import LanguageDetector from 'i18next-browser-languagedetector';
import ICU from 'i18next-icu';
import * as en from "i18n/en.json"
import * as zh from "i18n/zh.json"
import * as ja from "i18n/ja.json"
import * as ar from "i18n/ar.json"
import * as de from "i18n/de.json"
import * as es from "i18n/es.json"
import * as fr from "i18n/fr.json"
import * as it from "i18n/it.json"
import * as ko from "i18n/ko.json"
import * as pt from "i18n/pt.json"
import * as ru from "i18n/ru.json"
i18n.use(initReactI18next) // passes i18n down to react-i18next import "./i18n";
.use(LanguageDetector) import Homepage from "pages";
.use(ICU) import AboutPage from "pages/about";
.init({ import LicensePage from "pages/about/license";
resources: {
en: { const router = createBrowserRouter([
translation: en {
}, path: "/",
zh: { element: <Homepage />
translation: zh },
}, {
ja: { path: "about",
translation: ja element: <AboutPage />,
}, children: [
ar: { {
translation: ar path: "license",
}, element: <LicensePage />
de: {
translation: de
},
es: {
translation: es
},
fr: {
translation: fr
},
it: {
translation: it
},
ko: {
translation: ko
},
pt: {
translation: pt
},
ru: {
translation: ru
} }
}, ]
fallbackLng: "en", }
]);
interpolation: {
escapeValue: false // react already safes from xss => https://www.i18next.com/translation-function/interpolation#unescape
},
detection: {
order: ['navigator'],
caches: []
}
});
export function App() { export function App() {
return <Suspense fallback={<p>Loading...</p>}>{useRoutes(routes)}</Suspense>; return (
<div className="relative bg-white dark:bg-black dark:text-white min-h-screen w-screen">
<RouterProvider router={router} />
</div>
);
} }

65
src/i18n.ts Normal file
View File

@ -0,0 +1,65 @@
import * as en from "i18n/en.json"
import * as zh from "i18n/zh.json"
import * as ja from "i18n/ja.json"
import * as ar from "i18n/ar.json"
import * as de from "i18n/de.json"
import * as es from "i18n/es.json"
import * as fr from "i18n/fr.json"
import * as it from "i18n/it.json"
import * as ko from "i18n/ko.json"
import * as pt from "i18n/pt.json"
import * as ru from "i18n/ru.json"
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import LanguageDetector from 'i18next-browser-languagedetector';
import ICU from 'i18next-icu';
i18n.use(initReactI18next) // passes i18n down to react-i18next
.use(LanguageDetector)
.use(ICU)
.init({
resources: {
en: {
translation: en
},
zh: {
translation: zh
},
ja: {
translation: ja
},
ar: {
translation: ar
},
de: {
translation: de
},
es: {
translation: es
},
fr: {
translation: fr
},
it: {
translation: it
},
ko: {
translation: ko
},
pt: {
translation: pt
},
ru: {
translation: ru
}
},
fallbackLng: "en",
interpolation: {
escapeValue: false // react already safes from xss => https://www.i18next.com/translation-function/interpolation#unescape
},
detection: {
order: ['navigator'],
caches: []
}
});

View File

@ -1,6 +1,5 @@
import { StrictMode } from "react"; import { StrictMode } from "react";
import { createRoot } from "react-dom/client"; import { createRoot } from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import { App } from "./app"; import { App } from "./app";
import "./index.css"; import "./index.css";
import { NextUIProvider } from "@nextui-org/react"; import { NextUIProvider } from "@nextui-org/react";
@ -9,10 +8,8 @@ const app = createRoot(document.getElementById("root")!);
app.render( app.render(
<StrictMode> <StrictMode>
<BrowserRouter> <NextUIProvider>
<NextUIProvider> <App />
<App /> </NextUIProvider>
</NextUIProvider>
</BrowserRouter>
</StrictMode> </StrictMode>
); );

View File

@ -7,6 +7,7 @@ const config: Config = {
"./pages/**/*.{js,ts,jsx,tsx,mdx}", "./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}", "./components/**/*.{js,ts,jsx,tsx,mdx}",
"./node_modules/@nextui-org/theme/**/*.{js,ts,jsx,tsx}", "./node_modules/@nextui-org/theme/**/*.{js,ts,jsx,tsx}",
"./src/**/*.{js,ts,jsx,tsx,mdx}"
], ],
theme: { theme: {
extend: { extend: {

View File

@ -1,6 +1,5 @@
import { defineConfig } from "vite"; import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc"; import react from "@vitejs/plugin-react-swc";
import Pages from "vite-plugin-pages";
import tsconfigPaths from 'vite-tsconfig-paths'; import tsconfigPaths from 'vite-tsconfig-paths';
import { chunkSplitPlugin } from 'vite-plugin-chunk-split'; import { chunkSplitPlugin } from 'vite-plugin-chunk-split';
@ -8,9 +7,6 @@ import { chunkSplitPlugin } from 'vite-plugin-chunk-split';
export default defineConfig({ export default defineConfig({
plugins: [ plugins: [
react(), react(),
Pages({
dirs: "./pages/"
}),
tsconfigPaths(), tsconfigPaths(),
chunkSplitPlugin() chunkSplitPlugin()
] ]