diff --git a/bun.lockb b/bun.lockb
index 66f3594..caaa0da 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/components/engineSelector.tsx b/components/engineSelector.tsx
index b2c3464..7959fac 100644
--- a/components/engineSelector.tsx
+++ b/components/engineSelector.tsx
@@ -1,21 +1,22 @@
-import React, { useEffect, useState } from "react";
-import { Dropdown, DropdownTrigger, DropdownMenu, DropdownItem, Button } from "@nextui-org/react";
+import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { settingsAtom } from "lib/state/settings";
import { engineTranslation } from "lib/onesearch/translatedEngineList";
import { settingsType } from "global";
import { useAtomValue, useSetAtom } from "jotai";
+import Picker, { PickedItem } from "./picker";
export default function EngineSelector(props: { className: string }) {
const { t } = useTranslation();
const settings: settingsType = useAtomValue(settingsAtom);
- const items = settings.searchEngines;
+ const engines = settings.searchEngines;
const currentEngine: string = settings.currentSearchEngine;
- const displayEngine = getName(currentEngine);
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- const [selectedKeys, setSelectedKeys] = useState(new Set([currentEngine]) as any);
- const selectedValue = React.useMemo(() => Array.from(selectedKeys).join(", "), [selectedKeys]);
+ const [selected, setSelected] = useState(currentEngine);
const setSettings = useSetAtom(settingsAtom);
+ let engineList: PickedItem = {};
+ for (const engineKey of Object.keys(engines)) {
+ engineList[engineKey] = getName(engineKey);
+ }
function getName(engineKey: string) {
return engineTranslation.includes(engineKey) ? t(`search.engine.${engineKey}`) : engineKey;
@@ -30,34 +31,20 @@ export default function EngineSelector(props: { className: string }) {
};
});
}
- if (selectedValue !== currentEngine) {
- setEngine(selectedValue);
+ if (selected !== currentEngine) {
+ setEngine(selected);
}
- }, [currentEngine, selectedValue, setSettings]);
+ }, [currentEngine, selected, setSettings]);
return (
-
-
-
-
-
-
- {Object.keys(items).map((item) => (
-
- {getName(item)}
-
- ))}
-
-
-
+ {
+ setSelected(selected);
+ }}
+ displayContent={getName(selected)}
+ className={props.className}
+ />
);
}
diff --git a/components/onesearch/onesearch.tsx b/components/onesearch/onesearch.tsx
index 7f686f7..73aaaab 100644
--- a/components/onesearch/onesearch.tsx
+++ b/components/onesearch/onesearch.tsx
@@ -23,11 +23,11 @@ export default function OneSearch() {
const lastRequestTimeRef = useRef(0);
const selected = useAtomValue(selectedSuggestionAtom);
const settings = useAtomValue(settingsAtom);
- const devMode = true;
+ const devMode = false;
const query = useAtomValue(queryAtom);
const engineName = getSearchEngineName();
const engine = settings.currentSearchEngine;
- const { t } = useTranslation("Search");
+ const { t } = useTranslation();
const lang = i18next.language;
useEffect(() => {
@@ -127,7 +127,7 @@ export default function OneSearch() {
{s.suggestion}
- {t("search-help-text", { engine: engineName })}
+ {t("search.search-help-text", { engine: engineName })}
{devMode && (
diff --git a/components/onesearch/suggestionBox.tsx b/components/onesearch/suggestionBox.tsx
index 70637a5..e7cefb3 100644
--- a/components/onesearch/suggestionBox.tsx
+++ b/components/onesearch/suggestionBox.tsx
@@ -1,8 +1,10 @@
export default function SuggestionBox(props: { children?: React.ReactNode }) {
return (
-
+
{props.children}
);
diff --git a/components/picker.tsx b/components/picker.tsx
new file mode 100644
index 0000000..4b7e2dc
--- /dev/null
+++ b/components/picker.tsx
@@ -0,0 +1,148 @@
+import { HTMLAttributes, RefObject, useEffect, useRef, useState } from "react";
+import { selectedOnChange } from "./selectorItem";
+import { createPortal } from "react-dom";
+import { Icon } from "@iconify/react";
+import React from "react";
+
+export type selectionType = string;
+
+interface PickerProps extends HTMLAttributes
{
+ selected: selectionType;
+ selectionOnChange: selectedOnChange;
+ displayContent: string;
+ selectionItems: PickedItem;
+}
+
+export interface PickedItem {
+ [key: string]: selectionType;
+}
+
+export default function Picker(props: PickerProps) {
+ const itemListRef: RefObject = useRef(null);
+ const buttonRef: RefObject = useRef(null);
+ const [displayList, setDisplayList] = useState(false);
+
+ const updatePosition = () => {
+ if (itemListRef.current == null || buttonRef.current == null) {
+ return;
+ }
+ const buttonRect = buttonRef.current.getBoundingClientRect();
+ const listWidth = itemListRef.current.getBoundingClientRect().width;
+ // Align to center
+ itemListRef.current.style.left = buttonRect.x + buttonRect.width / 2 - listWidth / 2 + "px";
+ itemListRef.current.style.top = buttonRect.y + buttonRect.height + 16 + "px";
+ };
+
+ useEffect(() => {
+ updatePosition();
+ const handleResize = () => {
+ updatePosition();
+ };
+
+ window.addEventListener("resize", handleResize);
+
+ // Cleanup event listener on component unmount
+ return () => {
+ window.removeEventListener("resize", handleResize);
+ };
+ }, [itemListRef, buttonRef]);
+
+ function toggleDisplay(targetState?: boolean) {
+ function hideList() {
+ if (itemListRef.current) {
+ itemListRef.current.style.opacity = "0%";
+ itemListRef.current.style.transform = "scaleX(.85) scaleY(.85)";
+ }
+ setTimeout(() => {
+ setDisplayList(false);
+ }, 200);
+ }
+ function showList() {
+ setDisplayList(true);
+ setTimeout(() => {
+ updatePosition();
+ if (itemListRef.current) {
+ itemListRef.current.style.opacity = "100%";
+ itemListRef.current.style.transform = "scaleX(1) scaleY(1)";
+ }
+ }, 20);
+ }
+ if (targetState === true) {
+ showList();
+ } else if (targetState === false) {
+ hideList();
+ } else if (displayList === true) {
+ hideList();
+ } else {
+ showList();
+ }
+ }
+
+ const { displayContent, selectionOnChange, selectionItems, selected, ...rest } = props;
+ return (
+
+
+ {displayList && (
+
+ )}
+
+ );
+}
+
+interface PickerListProps {
+ selected: selectionType;
+ selectionOnChange: selectedOnChange;
+ selectionItems: PickedItem;
+ toggleDisplay: Function;
+}
+
+const PickerList = React.forwardRef((props, ref) => {
+ const { selected, selectionOnChange, selectionItems } = props;
+
+ return createPortal(
+
+ {Object.keys(selectionItems).map((key: string, index) => {
+ return (
+
{
+ selectionOnChange(key);
+ props.toggleDisplay();
+ }}
+ >
+
{selectionItems[key]}
+
+ {key === selected && (
+
+ )}
+
+ );
+ })}
+
,
+ document.body
+ );
+});
+
+PickerList.displayName = "PickerList";
diff --git a/components/selector.tsx b/components/selector.tsx
deleted file mode 100644
index ddc7f5d..0000000
--- a/components/selector.tsx
+++ /dev/null
@@ -1,85 +0,0 @@
-import { HTMLAttributes, ReactNode, RefObject, useEffect, useRef } from "react";
-import { selectedOnChange } from "./selectorItem";
-import { createPortal } from "react-dom";
-
-export type selectionType = string | number | boolean;
-
-interface PickerPropsChildrenStyle extends HTMLAttributes {
- selected: selectionType;
- selectionOnChange: selectedOnChange;
- displayContent: string;
- children: ReactNode;
-}
-
-interface PickerPropsParamStyle extends HTMLAttributes {
- selected: selectionType;
- selectionOnChange: selectedOnChange;
- displayContent: string;
- selectionItems: PickedItem;
-}
-
-interface PickedItem {
- [key: string]: selectionType;
-}
-
-export default function Picker(props: PickerPropsChildrenStyle | PickerPropsParamStyle) {
- const itemListRef: RefObject = useRef(null);
- const buttonRef: RefObject = useRef(null);
-
- const updatePosition = () => {
- if (itemListRef.current == null || buttonRef.current == null) {
- return;
- }
- const buttonRect = buttonRef.current.getBoundingClientRect();
- const listWidth = itemListRef.current.getBoundingClientRect().width;
- // Align to center
- itemListRef.current.style.left = buttonRect.x + buttonRect.width / 2 - listWidth / 2 + "px";
- itemListRef.current.style.top = buttonRect.y + buttonRect.height + 16 + "px";
- };
-
- useEffect(() => {
- updatePosition();
- const handleResize = () => {
- updatePosition();
- };
-
- window.addEventListener('resize', handleResize);
-
- // Cleanup event listener on component unmount
- return () => {
- window.removeEventListener('resize', handleResize);
- };
- }, [itemListRef, buttonRef]);
-
- if ("selectionItems" in props) {
- const { selectionItems, displayContent, selectionOnChange, ...rest } = props;
- return (
-
-
- {createPortal(
-
- {Object.keys(selectionItems).map((key: string, index) => {
- return
{selectionItems[key]}
;
- })}
-
,
- document.body
- )}
-
- );
- } else {
- return (
-
-
- {createPortal(
{props.children}
, document.body)}
-
- );
- }
-}
diff --git a/components/selectorItem.tsx b/components/selectorItem.tsx
index 94469fa..1365af0 100644
--- a/components/selectorItem.tsx
+++ b/components/selectorItem.tsx
@@ -1,5 +1,5 @@
import { ReactNode } from "react";
-import { selectionType } from "./selector";
+import { selectionType } from "./picker";
export type selectedOnChange = (target: selectionType) => void;
diff --git a/lib/onesearch/getSearchEngineName.ts b/lib/onesearch/getSearchEngineName.ts
index 4d6eac6..251a151 100644
--- a/lib/onesearch/getSearchEngineName.ts
+++ b/lib/onesearch/getSearchEngineName.ts
@@ -12,6 +12,6 @@ export default function(){
}
function getName(engineKey: string) {
- const { t } = useTranslation("Search");
- return engineTranslation.includes(engineKey) ? t(`engine.${engineKey}`) : engineKey;
+ const { t } = useTranslation();
+ return engineTranslation.includes(engineKey) ? t(`search.engine.${engineKey}`) : engineKey;
}
\ No newline at end of file
diff --git a/package.json b/package.json
index 4acdd13..25458a5 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,7 @@
"preview": "NODE_ENV=production bun server.ts"
},
"dependencies": {
+ "@iconify/react": "^5.0.1",
"@nextui-org/react": "^2.4.2",
"@types/express": "^4.17.21",
"cac": "^6.7.14",
diff --git a/pages/index.tsx b/pages/index.tsx
index 9219879..4fca8f9 100644
--- a/pages/index.tsx
+++ b/pages/index.tsx
@@ -6,7 +6,6 @@ import { settingsAtom } from "lib/state/settings";
import { bgFocusAtom } from "lib/state/background";
import EngineSelector from "components/engineSelector";
import OneSearch from "components/onesearch/onesearch";
-import Picker from "components/selector";
export default function Homepage() {
const settings = useAtomValue(settingsAtom);
@@ -16,20 +15,13 @@ export default function Homepage() {
setBgFocus(true)} />
- {}}
- displayContent="Item1"
- className="absolute w-fit h-8 top-20 lg:top-44 short:top-0 -translate-x-1/2 -translate-y-1 left-1/2 z-20"
- />
);
}
diff --git a/server.ts b/server.ts
index 46e9c34..b43687f 100644
--- a/server.ts
+++ b/server.ts
@@ -4,6 +4,7 @@ import ViteExpress from "vite-express";
import pjson from "./package.json";
import { networkInterfaces } from "os";
import cac from "cac";
+import { completeGoogle } from "search-engine-autocomplete";
const start = new Date();
const cli = cac();
@@ -37,6 +38,21 @@ if (parsed.options.host!==undefined && typeof parsed.options.host == "boolean" &
app.get("/message", (_, res) => res.send("Hello from express!"));
+app.get('/api/suggestion', async (req, res) => {
+ const query = req.query.q as string;
+ const t = parseInt(req.query.t as string || "0") || null;
+ let language = req.query.l as string || 'en-US';
+
+ try {
+ const data = await completeGoogle(query, language);
+ //logger.info({ type: "onesearch_search_autocomplete", query: query, data: data });
+ res.json({ ...data, time: t });
+ } catch (error) {
+ //logger.error({ type: "onesearch_search_autocomplete_error", error: error.message });
+ res.status(500).json({ error: 'Internal Server Error' });
+ }
+});
+
async function helloMessage() {
const { base } = await ViteExpress.getViteConfig();
//console.clear();