Skip to content

Commit 7be604a

Browse files
committed
allow to add custom search engine
1 parent c0d25b9 commit 7be604a

File tree

16 files changed

+289
-63
lines changed

16 files changed

+289
-63
lines changed

.vscode/settings.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"editor.formatOnSave": true,
33
"editor.codeActionsOnSave": {
4-
"source.organizeImports": true
4+
"source.organizeImports": "explicit"
55
}
66
}

src/assets/App.css

+19-3
Original file line numberDiff line numberDiff line change
@@ -886,19 +886,35 @@ Producthunt item
886886
order: 3;
887887
transition: opacity 0.3s ease-out, transform 0.3s ease-out, visibility 0.3s ease-out;
888888
width: 100%;
889+
display: flex;
890+
align-items: center;
889891
}
890892

891893
.dark .themeAdaptiveFillColor {
892-
fill: white;
894+
filter: invert(1);
893895
}
894896

895897
.searchBarIcon {
896898
position: absolute;
897899
height: 40px;
898900
margin: 0 16px;
899901
width: 24px;
900-
}
901-
902+
text-align: center;
903+
background: white;
904+
border-radius: 50%;
905+
width: 22px;
906+
height: 22px;
907+
padding: 2px;
908+
color: var(--tag-background-color);
909+
}
910+
/*.searchBarIcon > svg {
911+
background-color: white;
912+
color: black;
913+
padding: 2px;
914+
border-radius: 50%;
915+
width: 20px;
916+
height: 20px;
917+
}*/
902918
.searchBarInput {
903919
border-radius: 50px;
904920
color: var(--primary-text-color);

src/assets/chatgpt_logo.svg

+1
Loading

src/assets/variables.css

+2-1
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ html.dark {
7070
--chip-text: #fff;
7171
--chip-active-background: #0366d6;
7272
--chip-active-text: #fff;
73+
--chip-delete-button-color: #fff;
7374

7475
/* Step */
7576
--step-normal-background-color: none;
@@ -155,7 +156,7 @@ html.light {
155156
--chip-border-color: #e7eff7;
156157
--chip-active-background: #0366d6;
157158
--chip-active-text: #fff;
158-
159+
--chip-delete-button-color: #3c5065;
159160
/* Step */
160161
--step-normal-background-color: white;
161162
--step-normal-border-color: #cdd3db;

src/components/Elements/ChipsSet/ChipsSet.tsx

+17-5
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,28 @@
11
import clsx from 'clsx'
22
import { useState } from 'react'
3+
import { IoIosClose } from 'react-icons/io'
34
import { Option } from 'src/types'
45
import './chipset.css'
56
type ChipProps = {
67
option: Option
78
onSelect: (option: Option) => void
9+
onRemove?: (option: Option) => void
810
active: boolean
911
}
1012

11-
const Chip = ({ option, onSelect, active = false }: ChipProps) => {
13+
const Chip = ({ option, onSelect, onRemove, active = false }: ChipProps) => {
1214
return (
13-
<button className={'chip ' + (active && 'active')} onClick={() => onSelect(option)}>
14-
{option.icon && <span className="chipIcon">{option.icon}</span>}
15-
{option.label}
16-
</button>
15+
<div className={'chip ' + (active && 'active')}>
16+
<button onClick={() => onSelect(option)}>
17+
{option.icon && <span className="chipIcon">{option.icon}</span>}
18+
{option.label}
19+
</button>
20+
{option.removeable && onRemove && (
21+
<button className="deleteButton" onClick={() => onRemove(option)}>
22+
<IoIosClose className="icon" />
23+
</button>
24+
)}
25+
</div>
1726
)
1827
}
1928
type ChangeAction = {
@@ -26,13 +35,15 @@ type ChipsSetProps = {
2635
defaultValues?: string[]
2736
canSelectMultiple?: boolean
2837
onChange?: (changes: ChangeAction, options: Option[]) => void
38+
onRemove?: (option: Option) => void
2939
}
3040

3141
export const ChipsSet = ({
3242
className,
3343
options,
3444
canSelectMultiple = false,
3545
onChange,
46+
onRemove,
3647
defaultValues,
3748
}: ChipsSetProps) => {
3849
const [selectedChips, setSelectedChips] = useState<string[] | undefined>(defaultValues || [])
@@ -80,6 +91,7 @@ export const ChipsSet = ({
8091
key={option.value}
8192
option={option}
8293
onSelect={onSelect}
94+
onRemove={onRemove}
8395
active={selectedChips?.some((chipValue) => chipValue === option.value) || false}
8496
/>
8597
)

src/components/Elements/ChipsSet/chipset.css

+23-2
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,27 @@
1111
padding: 6px 12px;
1212
border: transparent;
1313
font-family: 'nunito';
14-
color: var(--chip-text);
1514
background-color: var(--chip-background);
1615
display: flex;
1716
align-items: center;
17+
column-gap: 2px;
18+
cursor: pointer;
19+
}
20+
.chip button {
21+
color: var(--chip-text);
22+
border: transparent;
23+
background: none;
24+
display: flex;
25+
align-items: center;
1826
column-gap: 8px;
1927
cursor: pointer;
28+
padding: 0;
29+
margin: 0;
30+
}
31+
.chip .deleteButton .icon {
32+
width: 20px;
33+
height: 20px;
34+
color: var(--chip-delete-button-color);
2035
}
2136
.chip:hover {
2237
filter: brightness(95%);
@@ -31,13 +46,17 @@
3146
.chip .chipIcon > svg {
3247
background-color: white;
3348
color: black;
34-
border-radius: 50px;
3549
padding: 2px;
50+
border-radius: 50%;
3651
width: 20px;
3752
height: 20px;
3853
}
3954
.chip.active {
4055
background-color: var(--chip-active-background);
56+
}
57+
58+
.chip.active button,
59+
.chip.active .deleteButton .icon {
4160
color: var(--chip-active-text);
4261
}
4362

@@ -46,5 +65,7 @@
4665
}
4766
.chipsSet.alternative-color .chip.active {
4867
background-color: var(--card-active-action-button-background);
68+
}
69+
.chipsSet.alternative-color .chip.active button {
4970
color: var(--card-active-action-button-color);
5071
}

src/components/Elements/SearchBar/SearchBar.tsx

+16-7
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
import React, { useEffect, useRef } from 'react'
2-
import { SUPPORTED_SEARCH_ENGINES } from 'src/config'
2+
import { IoSearchCircleSharp } from 'react-icons/io5'
33
import { trackSearchEngineUse } from 'src/lib/analytics'
44
import { useUserPreferences } from 'src/stores/preferences'
5-
import { SearchEngine } from 'src/types'
65

76
export const SearchBar = () => {
8-
const { searchEngine } = useUserPreferences()
7+
const { searchEngine, searchEngines } = useUserPreferences()
98

109
const keywordsInputRef = useRef<HTMLInputElement | null>(null)
11-
const usedSearchEngine: SearchEngine =
12-
SUPPORTED_SEARCH_ENGINES.find((engine) => engine.label === searchEngine) ||
13-
SUPPORTED_SEARCH_ENGINES[0]
10+
const usedSearchEngine =
11+
searchEngines.find((engine) => engine.label === searchEngine) || searchEngines[0]
1412

1513
const handleSubmit = (e: React.FormEvent) => {
1614
e.preventDefault()
@@ -28,7 +26,18 @@ export const SearchBar = () => {
2826

2927
return (
3028
<form className="searchBar" onSubmit={handleSubmit}>
31-
<usedSearchEngine.logo className={'searchBarIcon ' + usedSearchEngine.className} />
29+
{usedSearchEngine.default === false ? (
30+
<IoSearchCircleSharp className="searchBarIcon" />
31+
) : (
32+
<img
33+
className={'searchBarIcon'}
34+
src={`src/assets/${usedSearchEngine.label.toLowerCase()}_logo.svg`}
35+
onError={({ currentTarget }) => {
36+
currentTarget.onerror = null
37+
currentTarget.src = `src/assets/search_logo.svg`
38+
}}
39+
/>
40+
)}
3241
<input
3342
ref={keywordsInputRef}
3443
type="text"

src/components/Elements/SearchBarWithLogo/SearchBarWithLogo.tsx

+14-7
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,28 @@
1-
import { SUPPORTED_SEARCH_ENGINES } from 'src/config'
1+
import { IoIosSearch } from 'react-icons/io'
22
import { useUserPreferences } from 'src/stores/preferences'
33
import { SearchBar } from '../SearchBar/SearchBar'
44
import './SearchBarWithLogo.css'
55

66
export const SearchBarWithLogo = () => {
7-
const { searchEngine } = useUserPreferences()
8-
const userSearchEngine = SUPPORTED_SEARCH_ENGINES.find(
9-
(srchEngn) => srchEngn.label.toLocaleLowerCase() === searchEngine.toLocaleLowerCase()
10-
)
7+
const { searchEngine, searchEngines } = useUserPreferences()
8+
const userSearchEngine =
9+
searchEngines.find(
10+
(srchEngn) => srchEngn.label.toLocaleLowerCase() === searchEngine.toLocaleLowerCase()
11+
) || searchEngines[0]
1112

1213
return (
1314
<div className="searchBarWithLogo">
14-
{userSearchEngine?.logo && (
15+
{userSearchEngine.default === false ? (
1516
<div className="searchEngineLogo">
16-
<userSearchEngine.logo className={userSearchEngine?.className} />
17+
<IoIosSearch />
1718
</div>
19+
) : (
20+
<img
21+
className={'searchEngineLogo ' + userSearchEngine.className}
22+
src={`src/assets/${userSearchEngine.label.toLowerCase()}_logo.svg`}
23+
/>
1824
)}
25+
1926
<SearchBar />
2027
</div>
2128
)

src/components/Layout/SettingsLayout/SettingsLayout.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ export const SettingsLayout = () => {
1717
name: 'Bookmarks',
1818
path: '/settings/bookmarks',
1919
},
20-
20+
{
21+
name: 'Search Engine',
22+
path: '/settings/search-engine',
23+
},
2124
{
2225
name: 'Settings',
2326
path: '/settings/general',
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { useState } from 'react'
2+
import { isValidURL } from 'src/utils/UrlUtils'
3+
4+
import { TiPlus } from 'react-icons/ti'
5+
import { useUserPreferences } from 'src/stores/preferences'
6+
7+
export const AddSearchEngine = () => {
8+
const { addSearchEngine, searchEngines } = useUserPreferences()
9+
const [searchEngineUrl, setSearchEngineUrl] = useState<string | undefined>()
10+
const [RssInputFeedback, setRssInputFeedback] = useState<string | undefined>()
11+
12+
const onAddSearchEngine = async () => {
13+
if (!searchEngineUrl) {
14+
setRssInputFeedback('Please provide a valid Search engine URL')
15+
return
16+
}
17+
18+
if (!isValidURL(searchEngineUrl)) {
19+
setRssInputFeedback('Invalid Search Engine URL. Please check and try again')
20+
return
21+
}
22+
23+
if (searchEngines.some((se) => se.url === searchEngineUrl)) {
24+
setRssInputFeedback('Search Engine already exists')
25+
return
26+
}
27+
28+
// Get label from url
29+
const url = new URL(searchEngineUrl)
30+
const label = url.hostname.replace('www.', '').split('.')[0]
31+
32+
addSearchEngine({ label: label, url: searchEngineUrl, default: false })
33+
setRssInputFeedback('Search Engine added successfully')
34+
}
35+
36+
return (
37+
<div className="settingRow">
38+
<p className="settingTitle">Search Engine URL</p>
39+
<div className="settingContent">
40+
<div className="form">
41+
<input
42+
type="text"
43+
value={searchEngineUrl || ''}
44+
onChange={(e) => setSearchEngineUrl(e.target.value)}
45+
placeholder="https://google.com?q="
46+
/>
47+
<div>
48+
<button onClick={onAddSearchEngine}>
49+
<TiPlus /> Add
50+
</button>
51+
</div>
52+
</div>
53+
{RssInputFeedback && (
54+
<div className="settingHint">
55+
<p>{RssInputFeedback}</p>
56+
</div>
57+
)}
58+
</div>
59+
</div>
60+
)
61+
}

0 commit comments

Comments
 (0)