diff --git a/package.json b/package.json index add8574d..cf11f948 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@fontsource/montserrat": "^4.2.2", "@material-ui/core": "4.11.4", "@material-ui/icons": "4.11.2", + "fetch-jsonp": "^1.1.3", "react": "17.0.2", "react-clock": "3.0.0", "react-color-gradient-picker": "0.1.2", diff --git a/src/components/helpers/autocomplete/Autocomplete.jsx b/src/components/helpers/autocomplete/Autocomplete.jsx new file mode 100644 index 00000000..97f52b9f --- /dev/null +++ b/src/components/helpers/autocomplete/Autocomplete.jsx @@ -0,0 +1,77 @@ +import React from 'react'; + +import './autocomplete.scss'; + +export default class Autocomplete extends React.Component { + constructor(props) { + super(props); + this.state = { + filtered: [], + showList: false, + input: '' + }; + this.enabled = (localStorage.getItem('autocomplete') === 'true') + } + + onChange = (e) => { + if (this.enabled === false) { + return this.setState({ + input: e.target.value + }); + } + + this.setState({ + filtered: this.props.suggestions.filter((suggestion) => suggestion.toLowerCase().indexOf(e.target.value.toLowerCase()) > -1), + showList: true, + input: e.target.value + }); + + this.props.onChange(e.target.value); + }; + + onClick = (e) => { + this.setState({ + filtered: [], + showList: false, + input: e.target.innerText + }); + + this.props.onClick(e.target.innerText); + }; + + render() { + let autocomplete = null; + + if (this.state.showList && this.state.input) { + if (this.state.filtered.length && localStorage.getItem('autocomplete') === 'true') { + autocomplete = ( + + ); + } + } + + return ( + <> + + {autocomplete} + + ); + } +} diff --git a/src/components/helpers/autocomplete/autocomplete.scss b/src/components/helpers/autocomplete/autocomplete.scss new file mode 100644 index 00000000..db0fcbd6 --- /dev/null +++ b/src/components/helpers/autocomplete/autocomplete.scss @@ -0,0 +1,38 @@ +.suggestions { + text-align: left; + font-size: calc(5px + 1.2vmin); + background-color: rgba(0, 0, 0, 0.5); + border-top-width: 0; + list-style: none; + margin-top: 40px; + max-height: 143px; + overflow: hidden; + position: relative; + display: inline-block; + margin-left: 40px; + border-radius: 24px; + width: 430px; + opacity: 0; + + li { + padding: 0.5rem; + padding-left: 20px; + + &:hover { + background-color: rgba(0, 0, 0, 0.5); + cursor: pointer; + } + } +} + +.searchBar { + input[type=text]:focus+.suggestions { + opacity: 1; + } +} + +@media screen and (min-width: 1400px) { + .suggestions { + margin-top: 50px; + } +} diff --git a/src/components/modals/Modals.jsx b/src/components/modals/Modals.jsx index 053bfc4c..d0721f1f 100644 --- a/src/components/modals/Modals.jsx +++ b/src/components/modals/Modals.jsx @@ -48,10 +48,10 @@ export default class Modals extends React.PureComponent {
this.setState({ mainModal: false })}/> - this.closeWelcome()} isOpen={this.state.welcomeModal} className='Modal welcomemodal' overlayClassName='Overlay' ariaHideApp={false}> + this.closeWelcome()} isOpen={this.state.welcomeModal} className='Modal welcomemodal mainModal' overlayClassName='Overlay' ariaHideApp={false}> this.closeWelcome()}/> - this.setState({ feedbackModal: false })} isOpen={this.state.feedbackModal} className='Modal' overlayClassName='Overlay' ariaHideApp={false}> + this.setState({ feedbackModal: false })} isOpen={this.state.feedbackModal} className='Modal mainModal' overlayClassName='Overlay' ariaHideApp={false}> this.setState({ feedbackModal: false })}/> diff --git a/src/components/modals/main/scss/index.scss b/src/components/modals/main/scss/index.scss index 754ece30..7f27950f 100644 --- a/src/components/modals/main/scss/index.scss +++ b/src/components/modals/main/scss/index.scss @@ -32,13 +32,21 @@ } .modalLink { + display: block !important; + margin-top: 5px; color: var(--modal-link); cursor: pointer; - padding-left: 10px; &:hover { opacity: 0.8; } + + span { + font-size: 1.2rem; + color: var(--modal-link); + vertical-align: text-bottom; + margin-left: 5px; + } } .closeModal { @@ -102,12 +110,6 @@ } } -@media only screen and (min-height: 700px) { - .ReactModal__Content { - //min-height: 600px; - } -} - ul.sidebar { position: absolute; top: 0; diff --git a/src/components/modals/main/scss/settings/_main.scss b/src/components/modals/main/scss/settings/_main.scss index d3f92792..55feaf28 100644 --- a/src/components/modals/main/scss/settings/_main.scss +++ b/src/components/modals/main/scss/settings/_main.scss @@ -207,3 +207,16 @@ input::-webkit-inner-spin-button { input[type=number] { -moz-appearance: textfield; } + +.resetArea { + h2 { + font-size: 2rem !important; + } + h2, span, svg { + display: inline; + } + svg { + vertical-align: sub; + font-size: 1.4rem; + } +} \ No newline at end of file diff --git a/src/components/modals/main/settings/sections/Advanced.jsx b/src/components/modals/main/settings/sections/Advanced.jsx index 451ba494..fdcd1e83 100644 --- a/src/components/modals/main/settings/sections/Advanced.jsx +++ b/src/components/modals/main/settings/sections/Advanced.jsx @@ -53,7 +53,7 @@ export default class AdvancedSettings extends React.PureComponent {

{advanced.experimental_warning}

- this.setState({ resetModal: false })} isOpen={this.state.resetModal} className='Modal resetmodal' overlayClassName='Overlay resetoverlay' ariaHideApp={false}> + this.setState({ resetModal: false })} isOpen={this.state.resetModal} className='Modal resetmodal mainModal' overlayClassName='Overlay resetoverlay' ariaHideApp={false}> this.setState({ resetModal: false })} /> diff --git a/src/components/modals/main/settings/sections/Search.jsx b/src/components/modals/main/settings/sections/Search.jsx index 881c2fa7..44a54a34 100644 --- a/src/components/modals/main/settings/sections/Search.jsx +++ b/src/components/modals/main/settings/sections/Search.jsx @@ -3,6 +3,7 @@ import React from 'react'; import Dropdown from '../Dropdown'; import Checkbox from '../Checkbox'; import Switch from '../Switch'; +import Radio from '../Radio'; import EventBus from '../../../../../modules/helpers/eventbus'; @@ -10,6 +11,7 @@ import { isChrome } from 'react-device-detect'; import { toast } from 'react-toastify'; const searchEngines = require('../../../../widgets/search/search_engines.json'); +const autocompleteProviders = require('../../../../widgets/search/autocomplete_providers.json') export default class SearchSettings extends React.PureComponent { constructor() { @@ -76,6 +78,9 @@ export default class SearchSettings extends React.PureComponent {

{search.title}

{isChrome ? : null} + + +
this.setSearchEngine(value)}> {searchEngines.map((engine) => ( diff --git a/src/components/widgets/search/Search.jsx b/src/components/widgets/search/Search.jsx index 1cbadc8a..ac20e704 100644 --- a/src/components/widgets/search/Search.jsx +++ b/src/components/widgets/search/Search.jsx @@ -1,6 +1,9 @@ import React from 'react'; import EventBus from '../../../modules/helpers/eventbus'; +import fetchJSONP from 'fetch-jsonp'; + +import AutocompleteInput from '../../helpers/autocomplete/Autocomplete'; import SearchIcon from '@material-ui/icons/Search'; import MicIcon from '@material-ui/icons/Mic'; @@ -8,6 +11,7 @@ import MicIcon from '@material-ui/icons/Mic'; import './search.scss'; const searchEngines = require('./search_engines.json'); +const autocompleteProviders = require('./autocomplete_providers.json'); export default class Search extends React.PureComponent { constructor() { @@ -15,7 +19,11 @@ export default class Search extends React.PureComponent { this.state = { url: '', query: '', - microphone: null + autocompleteURL: '', + autocompleteQuery: '', + autocompleteCallback: '', + microphone: null, + suggestions: [] }; this.language = window.language.widgets.search; } @@ -42,6 +50,15 @@ export default class Search extends React.PureComponent { window.location.href = this.state.url + `?${this.state.query}=` + value; } + async getSuggestions(input) { + const data = await (await fetchJSONP(this.state.autocompleteURL + this.state.autocompleteQuery + input, { + jsonpCallback: this.state.autocompleteCallback + })).json(); + this.setState({ + suggestions: data[1].splice(0, 3) + }); + } + init() { let url; let query = 'q'; @@ -65,9 +82,23 @@ export default class Search extends React.PureComponent { microphone = ; } + let autocompleteURL; + let autocompleteQuery; + let autocompleteCallback; + + if (localStorage.getItem('autocomplete') === 'true') { + const info = autocompleteProviders.find((i) => i.value === localStorage.getItem('autocompleteProvider')); + autocompleteURL = info.url; + autocompleteQuery = info.query; + autocompleteCallback = info.callback; + } + this.setState({ url: url, query: query, + autocompleteURL: autocompleteURL, + autocompleteQuery: autocompleteQuery, + autocompleteCallback: autocompleteCallback, microphone: microphone }); } @@ -91,7 +122,7 @@ export default class Search extends React.PureComponent {
{this.state.microphone} - + this.getSuggestions(e)} onClick={this.searchButton}/> ); } diff --git a/src/components/widgets/search/autocomplete_providers.json b/src/components/widgets/search/autocomplete_providers.json new file mode 100644 index 00000000..292d5521 --- /dev/null +++ b/src/components/widgets/search/autocomplete_providers.json @@ -0,0 +1,17 @@ +[ + { + "name": "Google", + "value": "google", + "url": "https://www.google.com/complete/search?client=chrome", + "callback": "callback", + "query": "&q=" + }, + { + "name": "Bing", + "value": "bing", + "url": "https://api.bing.com/osjson.aspx?JsonType=callback", + "callback": "JsonCallback", + "query": "&query=" + } +] + \ No newline at end of file diff --git a/src/components/widgets/search/search.scss b/src/components/widgets/search/search.scss index 2bbb88ec..911b18ac 100644 --- a/src/components/widgets/search/search.scss +++ b/src/components/widgets/search/search.scss @@ -8,7 +8,7 @@ input[type=text] { width: 140px; - margin-left: 12px; + margin-left: 40px; border-radius: 24px; font-size: calc(5px + 1.2vmin); border: none; @@ -28,7 +28,8 @@ } .MuiSvgIcon-root { - margin-top: 4px; + position: absolute; + margin-top: 6px; font-size: 30px; filter: drop-shadow(0 0 6px rgba(0, 0, 0, 0.3)); cursor: pointer; diff --git a/src/modules/default_settings.json b/src/modules/default_settings.json index 19959443..9264b1e4 100644 --- a/src/modules/default_settings.json +++ b/src/modules/default_settings.json @@ -202,5 +202,9 @@ { "name": "datezero", "value": true + }, + { + "name": "autocompleteProvider", + "value": "google" } ] diff --git a/src/translations/de_DE.json b/src/translations/de_DE.json index 35a72dae..d11d763c 100644 --- a/src/translations/de_DE.json +++ b/src/translations/de_DE.json @@ -165,6 +165,8 @@ "title": "Suche", "search_engine": "Suchmaschine", "custom": "Abfrage-URL", + "autocomplete": "Autocomplete", + "autocomplete_provider": "Autocomplete Provider", "voice_search": "Sprachsuche" }, "weather": { diff --git a/src/translations/en_GB.json b/src/translations/en_GB.json index 4c928f92..a8f63b88 100644 --- a/src/translations/en_GB.json +++ b/src/translations/en_GB.json @@ -165,6 +165,8 @@ "title": "Search", "search_engine": "Search engine", "custom": "Custom search URL", + "autocomplete": "Autocomplete", + "autocomplete_provider": "Autocomplete Provider", "voice_search": "Voice search" }, "weather": { diff --git a/src/translations/en_US.json b/src/translations/en_US.json index 689d040f..8e6a1497 100644 --- a/src/translations/en_US.json +++ b/src/translations/en_US.json @@ -165,6 +165,8 @@ "title": "Search", "search_engine": "Search engine", "custom": "Custom search URL", + "autocomplete": "Autocomplete", + "autocomplete_provider": "Autocomplete Provider", "voice_search": "Voice search" }, "weather": { diff --git a/src/translations/es.json b/src/translations/es.json index ce7f6a99..25624d1e 100644 --- a/src/translations/es.json +++ b/src/translations/es.json @@ -165,6 +165,8 @@ "title": "Búsqueda", "search_engine": "Motor de búsqueda", "custom": "URL de búsqueda personalizada", + "autocomplete": "Autocomplete", + "autocomplete_provider": "Autocomplete Provider", "voice_search": "Búsqueda por voz" }, "weather": { diff --git a/src/translations/fr.json b/src/translations/fr.json index 2d1fe837..bcfe6659 100644 --- a/src/translations/fr.json +++ b/src/translations/fr.json @@ -165,6 +165,8 @@ "title": "Barre de Recherche", "search_engine": "Moteur de recherche", "custom": "URL de recherche personnalisée", + "autocomplete": "Autocomplete", + "autocomplete_provider": "Autocomplete Provider", "voice_search": "Recherche vocale" }, "weather": { diff --git a/src/translations/nl.json b/src/translations/nl.json index 0a618554..c655f4c7 100644 --- a/src/translations/nl.json +++ b/src/translations/nl.json @@ -165,6 +165,8 @@ "title": "Zoekbalk", "search_engine": "Zoekmachine", "custom": "Aangepaste zoekmachine-url", + "autocomplete": "Autocomplete", + "autocomplete_provider": "Autocomplete Provider", "voice_search": "Spraakgestuurd zoeken gebruiken" }, "weather": { diff --git a/src/translations/no.json b/src/translations/no.json index 476eaa41..26466a62 100644 --- a/src/translations/no.json +++ b/src/translations/no.json @@ -165,6 +165,8 @@ "title": "Søkebar", "search_engine": "Søkemotor", "custom": "Custom search URL", + "autocomplete": "Autocomplete", + "autocomplete_provider": "Autocomplete Provider", "voice_search": "Voice search" }, "weather": { diff --git a/src/translations/ru.json b/src/translations/ru.json index a5315774..79be8961 100644 --- a/src/translations/ru.json +++ b/src/translations/ru.json @@ -165,6 +165,8 @@ "title": "Панель поиска", "search_engine": "Поисковый движок", "custom": "Пользовательский поисковый движок", + "autocomplete": "Autocomplete", + "autocomplete_provider": "Autocomplete Provider", "voice_search": "Поиск голосом" }, "weather": { diff --git a/src/translations/zh_CN.json b/src/translations/zh_CN.json index 9271a7ba..3058c57d 100644 --- a/src/translations/zh_CN.json +++ b/src/translations/zh_CN.json @@ -165,6 +165,8 @@ "title": "搜索栏", "search_engine": "搜索引擎", "custom": "自定义搜索链接", + "autocomplete": "Autocomplete", + "autocomplete_provider": "Autocomplete Provider", "voice_search": "语音搜索" }, "weather": {