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 = (
+
+ {this.state.filtered.map((suggestion) => {
+ let className;
+
+ return (
+ -
+ {suggestion}
+
+ );
+ })}
+
+ );
+ }
+ }
+
+ 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 {
);
}
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": {