@ -2,7 +2,7 @@ import { useState, useEffect, useCallback, Fragment } from "react";
import { useTranslation } from "next-i18next" ;
import { FiSearch } from "react-icons/fi" ;
import { SiDuckduckgo , SiMicrosoftbing , SiGoogle , SiBaidu , SiBrave } from "react-icons/si" ;
import { Listbox , Transition } from "@headlessui/react" ;
import { Listbox , Transition , Combobox } from "@headlessui/react" ;
import classNames from "classnames" ;
import ContainerForm from "../widget/container_form" ;
@ -12,26 +12,31 @@ export const searchProviders = {
google : {
name : "Google" ,
url : "https://www.google.com/search?q=" ,
suggestionUrl : "https://www.google.com/complete/search?client=chrome&q=" ,
icon : SiGoogle ,
} ,
duckduckgo : {
name : "DuckDuckGo" ,
url : "https://duckduckgo.com/?q=" ,
suggestionUrl : "https://duckduckgo.com/ac/?type=list&q=" ,
icon : SiDuckduckgo ,
} ,
bing : {
name : "Bing" ,
url : "https://www.bing.com/search?q=" ,
suggestionUrl : "https://api.bing.com/osjson.aspx?query=" ,
icon : SiMicrosoftbing ,
} ,
baidu : {
name : "Baidu" ,
url : "https://www.baidu.com/s?wd=" ,
suggestionUrl : "http://suggestion.baidu.com/su?&action=opensearch&ie=utf-8&wd=" ,
icon : SiBaidu ,
} ,
brave : {
name : "Brave" ,
url : "https://search.brave.com/search?q=" ,
suggestionUrl : "https://search.brave.com/api/suggest?&rich=false&q=" ,
icon : SiBrave ,
} ,
custom : {
@ -72,6 +77,7 @@ export default function Search({ options }) {
const [ selectedProvider , setSelectedProvider ] = useState (
searchProviders [ availableProviderIds [ 0 ] ? ? searchProviders . google ] ,
) ;
const [ searchSuggestions , setSearchSuggestions ] = useState ( [ ] ) ;
useEffect ( ( ) => {
const storedProvider = getStoredProvider ( ) ;
@ -82,9 +88,40 @@ export default function Search({ options }) {
}
} , [ availableProviderIds ] ) ;
useEffect ( ( ) => {
const abortController = new AbortController ( ) ;
if (
options . showSearchSuggestions &&
( selectedProvider . suggestionUrl || options . suggestionUrl ) && / / c u s t o m p r o v i d e r s p a s s u r l v i a o p t i o n s
query . trim ( ) !== searchSuggestions [ 0 ]
) {
fetch ( ` /api/search/searchSuggestion?query= ${ encodeURIComponent ( query ) } &providerName= ${ selectedProvider . name } ` , {
signal : abortController . signal ,
} )
. then ( async ( searchSuggestionResult ) => {
const newSearchSuggestions = await searchSuggestionResult . json ( ) ;
if ( newSearchSuggestions ) {
if ( newSearchSuggestions [ 1 ] . length > 4 ) {
newSearchSuggestions [ 1 ] = newSearchSuggestions [ 1 ] . splice ( 0 , 4 ) ;
}
setSearchSuggestions ( newSearchSuggestions ) ;
}
} )
. catch ( ( ) => {
/ / I f t h e r e i s a n e r r o r , j u s t i g n o r e i t . T h e r e j u s t w i l l b e n o s e a r c h s u g g e s t i o n s .
} ) ;
}
return ( ) => {
abortController . abort ( ) ;
} ;
} , [ selectedProvider , options , query , searchSuggestions ] ) ;
const submitCallback = useCallback (
( event ) => {
const q = encodeURIComponent ( query ) ;
( valu e) => {
const q = encodeURIComponent ( value ) ;
const { url } = selectedProvider ;
if ( url ) {
window . open ( ` ${ url } ${ q } ` , options . target || "_blank" ) ;
@ -92,11 +129,9 @@ export default function Search({ options }) {
window . open ( ` ${ options . url } ${ q } ` , options . target || "_blank" ) ;
}
event . preventDefault ( ) ;
event . target . reset ( ) ;
setQuery ( "" ) ;
} ,
[ options. target , options . url , query , selectedProvider ] ,
[ selectedProvider, options . url , options . target ] ,
) ;
if ( ! availableProviderIds ) {
@ -109,11 +144,12 @@ export default function Search({ options }) {
} ;
return (
< ContainerForm options = { options } callback= { submitCallback } additionalClassNames= "grow information-widget-search" >
< ContainerForm options = { options } additionalClassNames= "grow information-widget-search" >
< Raw >
< div className = "flex-col relative h-8 my-4 min-w-fit ">
< div className = "flex-col relative h-8 my-4 min-w-fit z-20 ">
< div className = "flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none w-full text-theme-800 dark:text-white" / >
< input
< Combobox value = { query } onChange = { submitCallback } >
< Combobox.Input
type = "text"
className = "
overflow - hidden w - full h - full rounded - md
@ -124,7 +160,7 @@ export default function Search({ options }) {
focus : border - theme - 500 dark : focus : border - white / 50
border border - theme - 300 dark : border - theme - 200 / 50 "
placeholder = { t ( "search.placeholder" ) }
onChange = { ( s ) => setQuery ( s . currentT arget. value ) }
onChange = { ( event ) => setQuery ( event . t arget. value ) }
required
autoCapitalize = "off"
autoCorrect = "off"
@ -187,6 +223,32 @@ export default function Search({ options }) {
< / Listbox.Options >
< / Transition >
< / Listbox >
{ searchSuggestions [ 1 ] ? . length > 0 && (
< Combobox.Options className = "mt-1 rounded-md bg-theme-50 dark:bg-theme-800 border border-theme-300 dark:border-theme-200/30 cursor-pointer shadow-lg" >
< div className = "p-1 bg-white/50 dark:bg-white/10 text-theme-900/90 dark:text-white/90 text-xs" >
< Combobox.Option key = { query } value = { query } / >
{ searchSuggestions [ 1 ] . map ( ( suggestion ) => (
< Combobox.Option key = { suggestion } value = { suggestion } className = "flex w-full" >
{ ( { active } ) => (
< div
className = { classNames (
"px-2 py-1 rounded-md w-full flex-nowrap" ,
active ? "bg-theme-300/20 dark:bg-white/10" : "" ,
) }
>
< span className = "whitespace-pre" > { suggestion . indexOf ( query ) === 0 ? query : "" } < / span >
< span className = "mr-4 whitespace-pre opacity-50" >
{ suggestion . indexOf ( query ) === 0 ? suggestion . substring ( query . length ) : suggestion }
< / span >
< / div >
) }
< / Combobox.Option >
) ) }
< / div >
< / Combobox.Options >
) }
< / Combobox >
< / div >
< / Raw >
< / ContainerForm >