mirror of
https://github.com/mue/mue.git
synced 2026-07-17 05:54:14 +02:00
feat(SearchOptions): add Chrome policy warning for search engine selection removal
This commit is contained in:
@@ -1,94 +1,20 @@
|
||||
/* global chrome */
|
||||
import variables from 'config/variables';
|
||||
import { memo, createRef, useEffect, useState } from 'react';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
import { MdSearch, MdMic, MdScreenSearchDesktop } from 'react-icons/md';
|
||||
import { BsGoogle, BsBing } from 'react-icons/bs';
|
||||
import { SiDuckduckgo, SiBaidu, SiNaver } from 'react-icons/si';
|
||||
import { FaYandex, FaYahoo } from 'react-icons/fa';
|
||||
import { memo, createRef, useEffect, useState, useCallback } from 'react';
|
||||
import { MdSearch, MdMic } from 'react-icons/md';
|
||||
import { Tooltip } from 'components/Elements';
|
||||
import { Autocomplete as AutocompleteInput } from './components/autocomplete';
|
||||
|
||||
import EventBus from 'utils/eventbus';
|
||||
|
||||
import './search.scss';
|
||||
|
||||
import searchEngines from './search_engines.json';
|
||||
|
||||
function Search() {
|
||||
const [url, setURL] = useState('');
|
||||
const [query, setQuery] = useState('');
|
||||
const [microphone, setMicrophone] = useState(null);
|
||||
const [suggestions, setSuggestions] = useState([]);
|
||||
const [searchDropdown, setSearchDropdown] = useState(false);
|
||||
const [classList] = useState(
|
||||
localStorage.getItem('widgetStyle') === 'legacy' ? 'searchIcons old' : 'searchIcons',
|
||||
);
|
||||
const [currentSearch, setCurrentSearch] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
EventBus.on('refresh', (data) => {
|
||||
if (data === 'search') {
|
||||
init();
|
||||
}
|
||||
});
|
||||
init();
|
||||
if (localStorage.getItem('searchFocus') === 'true') {
|
||||
const element = document.getElementById('searchtext');
|
||||
if (element) {
|
||||
element.focus();
|
||||
}
|
||||
}
|
||||
return () => {
|
||||
EventBus.off('refresh');
|
||||
};
|
||||
}, [init]);
|
||||
|
||||
const micIcon = createRef();
|
||||
|
||||
const customText = variables
|
||||
.getMessage('modals.main.settings.sections.search.custom')
|
||||
.split(' ')[0];
|
||||
|
||||
function init() {
|
||||
let _url;
|
||||
let _query = 'q';
|
||||
|
||||
const setting = localStorage.getItem('searchEngine');
|
||||
const info = searchEngines.find((i) => i.settingsName === setting);
|
||||
|
||||
if (info !== undefined) {
|
||||
_url = info.url;
|
||||
if (info.query) {
|
||||
_query = info.query;
|
||||
}
|
||||
}
|
||||
|
||||
if (setting === 'custom') {
|
||||
const custom = localStorage.getItem('customSearchEngine');
|
||||
if (custom !== null) {
|
||||
_url = custom;
|
||||
}
|
||||
}
|
||||
|
||||
if (localStorage.getItem('voiceSearch') === 'true') {
|
||||
setMicrophone(
|
||||
<button
|
||||
className="navbarButton"
|
||||
onClick={startSpeechRecognition}
|
||||
ref={micIcon}
|
||||
aria-label="Microphone Search"
|
||||
>
|
||||
<MdMic className="micIcon" />
|
||||
</button>,
|
||||
);
|
||||
}
|
||||
|
||||
setURL(_url);
|
||||
setQuery(_query);
|
||||
setCurrentSearch(info ? info.name : 'Custom');
|
||||
}
|
||||
|
||||
function startSpeechRecognition() {
|
||||
const startSpeechRecognition = useCallback(() => {
|
||||
const voiceSearch = new window.webkitSpeechRecognition();
|
||||
voiceSearch.start();
|
||||
|
||||
@@ -108,98 +34,67 @@ function Search() {
|
||||
|
||||
setTimeout(() => {
|
||||
variables.stats.postEvent('feature', 'Voice search');
|
||||
window.location.href = url + `?${query}=` + searchText.value;
|
||||
// Use Chrome Search API - respects user's default search engine
|
||||
if (chrome && chrome.search && chrome.search.query) {
|
||||
chrome.search.query({
|
||||
text: searchText.value,
|
||||
disposition: 'CURRENT_TAB',
|
||||
}).catch((error) => {
|
||||
console.error('Search API error:', error);
|
||||
// Fallback to Google search if API fails
|
||||
window.location.href = `https://www.google.com/search?q=${encodeURIComponent(searchText.value)}`;
|
||||
});
|
||||
} else {
|
||||
// Fallback for browsers without chrome.search API
|
||||
window.location.href = `https://www.google.com/search?q=${encodeURIComponent(searchText.value)}`;
|
||||
}
|
||||
}, 1000);
|
||||
};
|
||||
}
|
||||
}, [micIcon]);
|
||||
|
||||
const init = useCallback(() => {
|
||||
if (localStorage.getItem('voiceSearch') === 'true') {
|
||||
setMicrophone(
|
||||
<button
|
||||
className="navbarButton"
|
||||
onClick={startSpeechRecognition}
|
||||
ref={micIcon}
|
||||
aria-label="Microphone Search"
|
||||
>
|
||||
<MdMic className="micIcon" />
|
||||
</button>,
|
||||
);
|
||||
}
|
||||
}, [micIcon, startSpeechRecognition]);
|
||||
|
||||
useEffect(() => {
|
||||
init();
|
||||
if (localStorage.getItem('searchFocus') === 'true') {
|
||||
const element = document.getElementById('searchtext');
|
||||
if (element) {
|
||||
element.focus();
|
||||
}
|
||||
}
|
||||
}, [init]);
|
||||
|
||||
function searchButton(e) {
|
||||
e.preventDefault();
|
||||
const value = e.target.value || document.getElementById('searchtext').value || 'mue fast';
|
||||
variables.stats.postEvent('feature', 'Search');
|
||||
window.location.href = url + `?${query}=` + value;
|
||||
}
|
||||
|
||||
async function getSuggestions(input) {
|
||||
if (input === '') {
|
||||
setSuggestions([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const results = await (
|
||||
await fetch(`${variables.constants.API_URL}/search/autocomplete?q=${input}`)
|
||||
).json();
|
||||
|
||||
try {
|
||||
setSuggestions(results.suggestions.splice(0, 5));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
// ignore error if empty
|
||||
}
|
||||
}
|
||||
|
||||
const getSuggestionsDebounced = useDebouncedCallback(getSuggestions, 100);
|
||||
// const getSuggestionsDebounced = getSuggestions;
|
||||
|
||||
/**
|
||||
* If the user selects a search engine from the dropdown menu, the function will set the state of the
|
||||
* search engine to the selected search engine.
|
||||
* @param {string} name - The name of the search engine
|
||||
* @param {boolean} custom - If the search engine is custom
|
||||
*/
|
||||
function setSearch(name, custom) {
|
||||
let _url;
|
||||
let _query = 'q';
|
||||
const info = searchEngines.find((i) => i.name === name);
|
||||
|
||||
if (info !== undefined) {
|
||||
_url = info.url;
|
||||
if (info.query) {
|
||||
_query = info.query;
|
||||
}
|
||||
}
|
||||
|
||||
if (custom) {
|
||||
const customSetting = localStorage.getItem('customSearchEngine');
|
||||
if (customSetting !== null) {
|
||||
_url = customSetting;
|
||||
} else {
|
||||
_url = url;
|
||||
}
|
||||
// Use Chrome Search API - respects user's default search engine
|
||||
if (chrome && chrome.search && chrome.search.query) {
|
||||
chrome.search.query({
|
||||
text: value,
|
||||
disposition: 'CURRENT_TAB',
|
||||
}).catch((error) => {
|
||||
console.error('Search API error:', error);
|
||||
// Fallback to Google search if API fails
|
||||
window.location.href = `https://www.google.com/search?q=${encodeURIComponent(value)}`;
|
||||
});
|
||||
} else {
|
||||
localStorage.setItem('searchEngine', info.settingsName);
|
||||
}
|
||||
|
||||
setURL(_url);
|
||||
setQuery(_query);
|
||||
setCurrentSearch(name);
|
||||
setSearchDropdown(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the icon for the search engine dropdown.
|
||||
* @param {string} name - The name of the search engine.
|
||||
* @returns A React component.
|
||||
*/
|
||||
function getSearchDropdownicon(name) {
|
||||
switch (name) {
|
||||
case 'Google':
|
||||
return <BsGoogle />;
|
||||
case 'DuckDuckGo':
|
||||
return <SiDuckduckgo />;
|
||||
case 'Bing':
|
||||
return <BsBing />;
|
||||
case 'Yahoo':
|
||||
case 'Yahoo! JAPAN':
|
||||
return <FaYahoo />;
|
||||
case 'Яндекс':
|
||||
return <FaYandex />;
|
||||
case '百度':
|
||||
return <SiBaidu />;
|
||||
case 'NAVER':
|
||||
return <SiNaver />;
|
||||
default:
|
||||
return <MdScreenSearchDesktop />;
|
||||
// Fallback for browsers without chrome.search API
|
||||
window.location.href = `https://www.google.com/search?q=${encodeURIComponent(value)}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,21 +102,6 @@ function Search() {
|
||||
<div className="searchComponents">
|
||||
<div className="searchMain">
|
||||
<div className={classList}>
|
||||
{localStorage.getItem('searchDropdown') === 'true' ? (
|
||||
<Tooltip
|
||||
title={variables.getMessage('modals.main.settings.sections.search.search_engine')}
|
||||
>
|
||||
<button
|
||||
className="navbarButton"
|
||||
aria-label="Search Engine"
|
||||
onClick={() => setSearchDropdown(!searchDropdown)}
|
||||
>
|
||||
{getSearchDropdownicon(currentSearch)}
|
||||
</button>
|
||||
</Tooltip>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
<Tooltip
|
||||
title={variables.getMessage('modals.main.settings.sections.search.voice_search')}
|
||||
>
|
||||
@@ -236,44 +116,14 @@ function Search() {
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<AutocompleteInput
|
||||
<input
|
||||
type="text"
|
||||
placeholder={variables.getMessage('widgets.search')}
|
||||
id="searchtext"
|
||||
suggestions={suggestions}
|
||||
onChange={(e) => getSuggestionsDebounced(e)}
|
||||
onClick={searchButton}
|
||||
className="searchInput"
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
<div>
|
||||
{localStorage.getItem('searchDropdown') === 'true' && searchDropdown === true && (
|
||||
<div className="searchDropdown">
|
||||
{searchEngines.map(({ name }, key) => {
|
||||
return (
|
||||
<span
|
||||
className={
|
||||
'searchDropdownList' +
|
||||
(currentSearch === name ? ' searchDropdownListActive' : '')
|
||||
}
|
||||
onClick={() => setSearch(name)}
|
||||
key={key}
|
||||
>
|
||||
{name}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
<span
|
||||
className={
|
||||
'searchDropdownList' +
|
||||
(currentSearch === customText ? ' searchDropdownListActive' : '')
|
||||
}
|
||||
onClick={() => setSearch(customText, 'custom')}
|
||||
>
|
||||
{customText}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,72 +1,32 @@
|
||||
import variables from 'config/variables';
|
||||
import { PureComponent } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { TextField } from '@mui/material';
|
||||
import { MdOutlineWarning } from 'react-icons/md';
|
||||
|
||||
import { Header, Row, Content, Action, PreferencesWrapper } from 'components/Layout/Settings';
|
||||
import { Dropdown, Checkbox } from 'components/Form/Settings';
|
||||
import { Checkbox } from 'components/Form/Settings';
|
||||
|
||||
import EventBus from 'utils/eventbus';
|
||||
|
||||
import searchEngines from '../search_engines.json';
|
||||
|
||||
class SearchOptions extends PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
customEnabled: false,
|
||||
customValue: localStorage.getItem('customSearchEngine') || '',
|
||||
};
|
||||
}
|
||||
|
||||
resetSearch() {
|
||||
localStorage.removeItem('customSearchEngine');
|
||||
this.setState({
|
||||
customValue: '',
|
||||
});
|
||||
|
||||
toast(variables.getMessage('toasts.reset'));
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (localStorage.getItem('searchEngine') === 'custom') {
|
||||
this.setState({
|
||||
customEnabled: true,
|
||||
});
|
||||
} else {
|
||||
localStorage.removeItem('customSearchEngine');
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
if (this.state.customEnabled === true && this.state.customValue !== '') {
|
||||
localStorage.setItem('customSearchEngine', this.state.customValue);
|
||||
}
|
||||
|
||||
EventBus.emit('refresh', 'search');
|
||||
}
|
||||
|
||||
setSearchEngine(input) {
|
||||
if (input === 'custom') {
|
||||
this.setState({
|
||||
customEnabled: true,
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
customEnabled: false,
|
||||
});
|
||||
localStorage.setItem('searchEngine', input);
|
||||
}
|
||||
|
||||
EventBus.emit('refresh', 'search');
|
||||
}
|
||||
|
||||
render() {
|
||||
const SEARCH_SECTION = 'modals.main.settings.sections.search';
|
||||
|
||||
const ChromePolicyWarning = () => {
|
||||
return (
|
||||
<div className="itemWarning" style={{ marginBottom: '20px' }}>
|
||||
<MdOutlineWarning />
|
||||
<div className="text">
|
||||
<span className="header">Search Engine Selection Removed</span>
|
||||
<span>{variables.getMessage(`${SEARCH_SECTION}.chrome_policy_warning`)}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const AdditionalOptions = () => {
|
||||
return (
|
||||
<Row>
|
||||
<Row final={true}>
|
||||
<Content
|
||||
title={variables.getMessage('modals.main.settings.additional_settings')}
|
||||
subtitle={variables.getMessage(`${SEARCH_SECTION}.additional`)}
|
||||
@@ -80,74 +40,12 @@ class SearchOptions extends PureComponent {
|
||||
category="search"
|
||||
/>
|
||||
) : null}
|
||||
<Checkbox
|
||||
name="searchDropdown"
|
||||
text={variables.getMessage(`${SEARCH_SECTION}.dropdown`)}
|
||||
category="search"
|
||||
element=".other"
|
||||
/>
|
||||
<Checkbox
|
||||
name="searchFocus"
|
||||
text={variables.getMessage(`${SEARCH_SECTION}.focus`)}
|
||||
category="search"
|
||||
element=".other"
|
||||
/>
|
||||
<Checkbox
|
||||
name="autocomplete"
|
||||
text={variables.getMessage(`${SEARCH_SECTION}.autocomplete`)}
|
||||
category="search"
|
||||
/>
|
||||
</Action>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
const SearchEngineSelection = () => {
|
||||
return (
|
||||
<Row final={!this.state.customEnabled}>
|
||||
<Content
|
||||
title={variables.getMessage(`${SEARCH_SECTION}.search_engine`)}
|
||||
subtitle={variables.getMessage(
|
||||
'modals.main.settings.sections.search.search_engine_subtitle',
|
||||
)}
|
||||
/>
|
||||
<Action>
|
||||
<Dropdown
|
||||
name="searchEngine"
|
||||
onChange={(value) => this.setSearchEngine(value)}
|
||||
items={[
|
||||
...searchEngines.map((engine) => ({
|
||||
value: engine.settingsName,
|
||||
text: engine.name,
|
||||
})),
|
||||
{
|
||||
value: 'custom',
|
||||
text: variables.getMessage(`${SEARCH_SECTION}.custom`),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Action>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
const CustomOptions = () => {
|
||||
return (
|
||||
<Row final={true}>
|
||||
<Content title={variables.getMessage(`${SEARCH_SECTION}.custom`)} />
|
||||
<Action>
|
||||
<TextField
|
||||
label={variables.getMessage(`${SEARCH_SECTION}.custom`)}
|
||||
value={this.state.customValue}
|
||||
onInput={(e) => this.setState({ customValue: e.target.value })}
|
||||
varient="outlined"
|
||||
InputLabelProps={{ shrink: true }}
|
||||
/>
|
||||
<p style={{ marginTop: '0px' }}>
|
||||
<span className="link" onClick={() => this.resetSearch()}>
|
||||
{variables.getMessage('modals.main.settings.buttons.reset')}
|
||||
</span>
|
||||
</p>
|
||||
</Action>
|
||||
</Row>
|
||||
);
|
||||
@@ -162,9 +60,8 @@ class SearchOptions extends PureComponent {
|
||||
visibilityToggle={true}
|
||||
/>
|
||||
<PreferencesWrapper setting="searchBar" category="widgets" visibilityToggle={true}>
|
||||
<ChromePolicyWarning />
|
||||
<AdditionalOptions />
|
||||
<SearchEngineSelection />
|
||||
{this.state.customEnabled && CustomOptions()}
|
||||
</PreferencesWrapper>
|
||||
</>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user