diff --git a/manifest/chrome.json b/manifest/chrome.json index 16219f1f..a9866d70 100644 --- a/manifest/chrome.json +++ b/manifest/chrome.json @@ -6,6 +6,7 @@ "description": "__MSG_description__", "version": "7.1.2", "homepage_url": "https://muetab.com", + "permissions": ["search"], "action": { "default_icon": "icons/128x128.png" }, diff --git a/src/components/Elements/MainModal/scss/settings/_main.scss b/src/components/Elements/MainModal/scss/settings/_main.scss index f77e041c..f43b0126 100644 --- a/src/components/Elements/MainModal/scss/settings/_main.scss +++ b/src/components/Elements/MainModal/scss/settings/_main.scss @@ -144,3 +144,40 @@ h4 { pointer-events: none; transition: 0.4s ease-in-out; } + +// Warning banner (used in Search settings and potentially others) +.itemWarning { + padding: 10px 20px; + display: flex; + flex-flow: row; + gap: 15px; + align-items: center; + + .text { + display: flex; + flex-flow: column; + + .header { + font-weight: 600; + margin-bottom: 5px; + } + } + + svg { + @include themed { + background-image: t($slightGradient); + box-shadow: t($boxShadow); + } + + padding: 7px; + border-radius: 100%; + font-size: 24px; + min-width: 24px; + } + + @include themed { + background: t($modal-sidebar); + border-radius: t($borderRadius); + box-shadow: 0 0 0 1px t($modal-sidebarActive); + } +} diff --git a/src/features/search/Search.jsx b/src/features/search/Search.jsx index 5822d8cd..b323c4e4 100644 --- a/src/features/search/Search.jsx +++ b/src/features/search/Search.jsx @@ -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( - , - ); - } - - 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( + , + ); + } + }, [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 ; - case 'DuckDuckGo': - return ; - case 'Bing': - return ; - case 'Yahoo': - case 'Yahoo! JAPAN': - return ; - case 'Яндекс': - return ; - case '百度': - return ; - case 'NAVER': - return ; - default: - return ; + // Fallback for browsers without chrome.search API + window.location.href = `https://www.google.com/search?q=${encodeURIComponent(value)}`; } } @@ -207,21 +102,6 @@ function Search() {
- {localStorage.getItem('searchDropdown') === 'true' ? ( - - - - ) : ( - '' - )} @@ -236,44 +116,14 @@ function Search() {
- getSuggestionsDebounced(e)} - onClick={searchButton} + className="searchInput" />
-
- {localStorage.getItem('searchDropdown') === 'true' && searchDropdown === true && ( -
- {searchEngines.map(({ name }, key) => { - return ( - setSearch(name)} - key={key} - > - {name} - - ); - })} - setSearch(customText, 'custom')} - > - {customText} - -
- )} -
); } diff --git a/src/features/search/options/SearchOptions.jsx b/src/features/search/options/SearchOptions.jsx index 2668eb45..77b545fb 100644 --- a/src/features/search/options/SearchOptions.jsx +++ b/src/features/search/options/SearchOptions.jsx @@ -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 ( +
+ +
+ Search Engine Selection Removed + {variables.getMessage(`${SEARCH_SECTION}.chrome_policy_warning`)} +
+
+ ); + }; + const AdditionalOptions = () => { return ( - + ) : null} - - - - - ); - }; - - const SearchEngineSelection = () => { - return ( - - - - this.setSearchEngine(value)} - items={[ - ...searchEngines.map((engine) => ({ - value: engine.settingsName, - text: engine.name, - })), - { - value: 'custom', - text: variables.getMessage(`${SEARCH_SECTION}.custom`), - }, - ]} - /> - - - ); - }; - - const CustomOptions = () => { - return ( - - - - this.setState({ customValue: e.target.value })} - varient="outlined" - InputLabelProps={{ shrink: true }} - /> -

- this.resetSearch()}> - {variables.getMessage('modals.main.settings.buttons.reset')} - -

); @@ -162,9 +60,8 @@ class SearchOptions extends PureComponent { visibilityToggle={true} /> + - - {this.state.customEnabled && CustomOptions()} ); diff --git a/src/i18n/locales/en_GB.json b/src/i18n/locales/en_GB.json index d1e9fcf5..e2ca604d 100644 --- a/src/i18n/locales/en_GB.json +++ b/src/i18n/locales/en_GB.json @@ -285,7 +285,8 @@ "autocomplete_provider_subtitle": "Search engine to use for autocomplete dropdown results", "voice_search": "Voice search", "dropdown": "Search Dropdown", - "focus": "Focus on tab open" + "focus": "Focus on tab open", + "chrome_policy_warning": "In version 7.2, the ability to choose your search engine within Mue was removed to comply with new Chrome Web Store guidelines. Mue now uses your browser's default search engine. You can change this in Chrome Settings." }, "weather": { "title": "Weather",