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
- this.resetSearch()}> - {variables.getMessage('modals.main.settings.buttons.reset')} - -