From 171dbb7509afe95b9f26adf755572ac92be85c79 Mon Sep 17 00:00:00 2001 From: Gabe Date: Fri, 3 Oct 2025 18:28:50 +0800 Subject: [PATCH] feat: support youdao dict --- public/manifest.json | 2 +- src/apis/index.js | 71 +++++++++++ src/background.js | 87 ++++++++++--- src/config/api.js | 11 +- src/config/i18n.js | 10 ++ src/config/setting.js | 4 + src/hooks/Fetch.js | 172 ++++++++++++++++++++----- src/hooks/I18n.js | 4 +- src/index.js | 7 +- src/views/Options/About.js | 2 +- src/views/Options/FavWords.js | 7 +- src/views/Options/Playground.js | 3 +- src/views/Options/Setting.js | 16 ++- src/views/Options/Tranbox.js | 23 +++- src/views/Selection/DictCont.js | 140 +++++++++++---------- src/views/Selection/SugCont.js | 79 +++++++++--- src/views/Selection/TranBox.js | 4 +- src/views/Selection/TranForm.js | 214 ++++++++++++++++++++------------ src/views/Selection/index.js | 3 - 19 files changed, 631 insertions(+), 228 deletions(-) diff --git a/public/manifest.json b/public/manifest.json index d1a55dc..0cb84e3 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -45,7 +45,7 @@ "description": "__MSG_open_options__" } }, - "permissions": ["storage", "contextMenus", "scripting", "declarativeNetRequest"], + "permissions": ["storage", "contextMenus", "scripting", "declarativeNetRequest", "declarativeNetRequestWithHostAccess"], "host_permissions": [""], "icons": { "16": "images/logo16.png", diff --git a/src/apis/index.js b/src/apis/index.js index 3e677d1..57a3c3f 100644 --- a/src/apis/index.js +++ b/src/apis/index.js @@ -159,6 +159,77 @@ export const apiBaiduSuggest = async (text) => { return []; }; +/** + * 有道翻译建议 + * @param {*} text + * @returns + */ +export const apiYoudaoSuggest = async (text) => { + const params = { + num: 5, + ver: 3.0, + doctype: "json", + cache: false, + le: "en", + q: text, + }; + const input = `https://dict.youdao.com/suggest?${queryString.stringify(params)}`; + const init = { + headers: { + accept: "application/json, text/plain, */*", + "accept-language": "en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7,ja;q=0.6", + "content-type": "application/x-www-form-urlencoded", + }, + method: "GET", + }; + const res = await fetchData(input, init, { useCache: true }); + + if (res?.result?.code === 200) { + await putHttpCachePolyfill(input, init, res); + return res.data.entries; + } + + return []; +}; + +/** + * 有道词典 + * @param {*} text + * @returns + */ +export const apiYoudaoDict = async (text) => { + const params = { + doctype: "json", + jsonversion: 4, + }; + const input = `https://dict.youdao.com/jsonapi_s?${queryString.stringify(params)}`; + const body = queryString.stringify({ + q: "search", + le: "en", + t: 3, + client: "web", + // sign: "", + keyfrom: "webdict", + }); + const init = { + headers: { + accept: "application/json, text/plain, */*", + "accept-language": "en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7,ja;q=0.6", + "content-type": "application/x-www-form-urlencoded", + }, + method: "POST", + body, + }; + const res = await fetchData(input, init, { useCache: true }); + + if (res) { + await putHttpCachePolyfill(input, init, res); + return res; + } + + return null; +}; + /** * 百度语音 * @param {*} text diff --git a/src/background.js b/src/background.js index cfee181..1a573f0 100644 --- a/src/background.js +++ b/src/background.js @@ -14,6 +14,7 @@ import { MSG_INJECT_CSS, MSG_UPDATE_CSP, DEFAULT_CSPLIST, + DEFAULT_ORILIST, CMD_TOGGLE_TRANSLATE, CMD_TOGGLE_STYLE, CMD_OPEN_OPTIONS, @@ -33,7 +34,9 @@ import { kissLog } from "./libs/log"; globalThis.ContextType = "BACKGROUND"; -const REMOVE_HEADERS = [ +const CSP_RULE_START_ID = 1; +const ORI_RULE_START_ID = 10000; +const CSP_REMOVE_HEADERS = [ `content-security-policy`, `content-security-policy-report-only`, `x-webkit-csp`, @@ -94,17 +97,34 @@ async function addContextMenus(contextMenuType = 1) { * 更新CSP策略 * @param {*} csplist */ -async function updateCspRules(csplist = DEFAULT_CSPLIST.join(",\n")) { +async function updateCspRules({ csplist, orilist }) { try { - const newRules = csplist - .split(/\n|,/) - .map((url) => url.trim()) - .filter(Boolean) - .map((url, idx) => ({ - id: idx + 1, + const oldRules = await browser.declarativeNetRequest.getDynamicRules(); + + const rulesToAdd = []; + const idsToRemove = []; + + if (csplist !== undefined) { + let processedCspList = csplist; + if (typeof processedCspList === "string") { + processedCspList = processedCspList + .split(/\n|,/) + .map((url) => url.trim()) + .filter(Boolean); + } + + const oldCspRuleIds = oldRules + .filter( + (rule) => rule.id >= CSP_RULE_START_ID && rule.id < ORI_RULE_START_ID + ) + .map((rule) => rule.id); + idsToRemove.push(...oldCspRuleIds); + + const newCspRules = processedCspList.map((url, index) => ({ + id: CSP_RULE_START_ID + index, action: { type: "modifyHeaders", - responseHeaders: REMOVE_HEADERS.map((header) => ({ + responseHeaders: CSP_REMOVE_HEADERS.map((header) => ({ operation: "remove", header, })), @@ -114,12 +134,43 @@ async function updateCspRules(csplist = DEFAULT_CSPLIST.join(",\n")) { resourceTypes: ["main_frame", "sub_frame"], }, })); - const oldRules = await browser.declarativeNetRequest.getDynamicRules(); - const oldRuleIds = oldRules.map((rule) => rule.id); - await browser.declarativeNetRequest.updateDynamicRules({ - removeRuleIds: oldRuleIds, - addRules: newRules, - }); + rulesToAdd.push(...newCspRules); + } + + if (orilist !== undefined) { + let processedOriList = orilist; + if (typeof processedOriList === "string") { + processedOriList = processedOriList + .split(/\n|,/) + .map((url) => url.trim()) + .filter(Boolean); + } + + const oldOriRuleIds = oldRules + .filter((rule) => rule.id >= ORI_RULE_START_ID) + .map((rule) => rule.id); + idsToRemove.push(...oldOriRuleIds); + + const newOriRules = processedOriList.map((url, index) => ({ + id: ORI_RULE_START_ID + index, + action: { + type: "modifyHeaders", + requestHeaders: [{ header: "Origin", operation: "remove" }], + }, + condition: { + urlFilter: url, + resourceTypes: ["xmlhttprequest"], + }, + })); + rulesToAdd.push(...newOriRules); + } + + if (idsToRemove.length > 0 || rulesToAdd.length > 0) { + await browser.declarativeNetRequest.updateDynamicRules({ + removeRuleIds: idsToRemove, + addRules: rulesToAdd, + }); + } } catch (err) { kissLog("update csp rules", err); } @@ -149,7 +200,7 @@ browser.runtime.onInstalled.addListener(() => { addContextMenus(); // 禁用CSP - updateCspRules(); + updateCspRules({ csplist: DEFAULT_CSPLIST, orilist: DEFAULT_ORILIST }); }); /** @@ -159,7 +210,7 @@ browser.runtime.onStartup.addListener(async () => { // 同步数据 await trySyncSettingAndRules(); - const { clearCache, contextMenuType, subrulesList, csplist } = + const { clearCache, contextMenuType, subrulesList, csplist, orilist } = await getSettingWithDefault(); // 清除缓存 @@ -177,7 +228,7 @@ browser.runtime.onStartup.addListener(async () => { addContextMenus(contextMenuType); // 禁用CSP - updateCspRules(csplist); + updateCspRules({ csplist, orilist }); // 同步订阅规则 trySyncAllSubRules({ subrulesList }); diff --git a/src/config/api.js b/src/config/api.js index ad5dfdb..6fa0e96 100644 --- a/src/config/api.js +++ b/src/config/api.js @@ -14,6 +14,14 @@ export const INPUT_PLACE_KEY = "{{key}}"; // 占位符 export const INPUT_PLACE_MODEL = "{{model}}"; // 占位符 export const OPT_DICT_BAIDU = "Baidu"; +export const OPT_DICT_YOUDAO = "Youdao"; +export const OPT_DICT_ALL = [OPT_DICT_BAIDU, OPT_DICT_YOUDAO]; +export const OPT_DICT_MAP = new Set(OPT_DICT_ALL); + +export const OPT_SUG_BAIDU = "Baidu"; +export const OPT_SUG_YOUDAO = "Youdao"; +export const OPT_SUG_ALL = [OPT_SUG_BAIDU, OPT_SUG_YOUDAO]; +export const OPT_SUG_MAP = new Set(OPT_SUG_ALL); export const OPT_TRANS_GOOGLE = "Google"; export const OPT_TRANS_GOOGLE_2 = "Google2"; @@ -63,9 +71,6 @@ export const OPT_LANGDETECTOR_ALL = [ OPT_TRANS_TENCENT, ]; -export const OPT_DICT_ALL = [OPT_TRANS_BAIDU]; -export const OPT_DICT_MAP = new Set(OPT_DICT_ALL); - // 翻译引擎特殊集合 export const API_SPE_TYPES = { // 内置翻译 diff --git a/src/config/i18n.js b/src/config/i18n.js index f764c39..0282bf2 100644 --- a/src/config/i18n.js +++ b/src/config/i18n.js @@ -1148,6 +1148,11 @@ export const I18N = { en: `Translate Blacklist`, zh_TW: `停用翻譯名單`, }, + disabled_orilist: { + zh: `禁用Origin名单`, + en: `Disabled Origin List`, + zh_TW: `停用 Origin 名單`, + }, disabled_csplist: { zh: `禁用CSP名单`, en: `Disabled CSP List`, @@ -1323,6 +1328,11 @@ export const I18N = { en: `English Dictionary`, zh_TW: `英文字典`, }, + english_suggest: { + zh: `英文建议`, + en: `English Suggest`, + zh_TW: `英文建議`, + }, api_name: { zh: `接口名称`, en: `API Name`, diff --git a/src/config/setting.js b/src/config/setting.js index bb04580..104ebbc 100644 --- a/src/config/setting.js +++ b/src/config/setting.js @@ -1,5 +1,6 @@ import { OPT_DICT_BAIDU, + OPT_SUG_BAIDU, DEFAULT_HTTP_TIMEOUT, OPT_TRANS_MICROSOFT, DEFAULT_API_LIST, @@ -28,6 +29,7 @@ export const DEFAULT_BLACKLIST = [ "login.dingtalk.com", ]; // 禁用翻译名单 export const DEFAULT_CSPLIST = ["https://github.com"]; // 禁用CSP名单 +export const DEFAULT_ORILIST = ["https://dict.youdao.com"]; // 移除Origin名单 // 同步设置 export const OPT_SYNCTYPE_WORKER = "KISS-Worker"; @@ -90,6 +92,7 @@ export const DEFAULT_TRANBOX_SETTING = { triggerMode: OPT_TRANBOX_TRIGGER_CLICK, // 触发翻译方式 // extStyles: "", // 附加样式 enDict: OPT_DICT_BAIDU, // 英文词典 + enSug: OPT_SUG_BAIDU, // 英文建议 }; // 订阅列表 @@ -143,6 +146,7 @@ export const DEFAULT_SETTING = { touchTranslate: 2, // 触屏翻译 blacklist: DEFAULT_BLACKLIST.join(",\n"), // 禁用翻译名单 csplist: DEFAULT_CSPLIST.join(",\n"), // 禁用CSP名单 + orilist: DEFAULT_ORILIST.join(",\n"), // 禁用CSP名单 // disableLangs: [], // 不翻译的语言(移至rule,作废) skipLangs: [], // 不翻译的语言(从rule移回) transInterval: 100, // 翻译等待时间 diff --git a/src/hooks/Fetch.js b/src/hooks/Fetch.js index a265134..59b1c6d 100644 --- a/src/hooks/Fetch.js +++ b/src/hooks/Fetch.js @@ -1,40 +1,152 @@ -import { useEffect, useState } from "react"; +import { useEffect, useState, useCallback } from "react"; -/** - * fetch data hook - * @returns - */ -export const useFetch = (url) => { +export const useAsync = () => { const [data, setData] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); - useEffect(() => { - if (!url) { + const execute = useCallback(async (fn, ...args) => { + if (!fn) { return; } - (async () => { - setLoading(true); - try { - const res = await fetch(url); - if (!res.ok) { - throw new Error(`[${res.status}] ${res.statusText}`); - } - let data; - if (res.headers.get("Content-Type")?.includes("json")) { - data = await res.json(); - } else { - data = await res.text(); - } - setData(data); - } catch (err) { - setError(err); - } finally { - setLoading(false); - } - })(); - }, [url]); + setLoading(true); + setError(null); - return [data, loading, error]; + try { + const res = await fn(...args); + setData(res); + setLoading(false); + return res; + } catch (err) { + setError(err?.message || "An unknown error occurred"); + setLoading(false); + // throw err; + } + }, []); + + const reset = useCallback(() => { + setData(null); + setLoading(false); + setError(null); + }, []); + + return { data, loading, error, execute, reset }; +}; + +export const useAsyncNow = (fn, arg) => { + const { execute, ...asyncState } = useAsync(); + + useEffect(() => { + if (fn) { + execute(fn, arg); + } + }, [execute, fn, arg]); + + return { ...asyncState }; +}; + +export const useFetch = () => { + const { execute, ...asyncState } = useAsync(); + + const requester = useCallback(async (url, options) => { + const response = await fetch(url, options); + if (!response.ok) { + const errorInfo = await response.text(); + throw new Error( + `Request failed: ${response.status} ${response.statusText} - ${errorInfo}` + ); + } + if (response.status === 204) { + return null; + } + + if (response.headers.get("Content-Type")?.includes("json")) { + return response.json(); + } + + return response.text(); + }, []); + + const get = useCallback( + async (url, options = {}) => { + try { + const result = await execute(requester, url, { + ...options, + method: "GET", + }); + return result; + } catch (err) { + return null; + } + }, + [execute, requester] + ); + + const post = useCallback( + async (url, body, options = {}) => { + try { + const result = await execute(requester, url, { + ...options, + method: "POST", + headers: { "Content-Type": "application/json", ...options.headers }, + body: JSON.stringify(body), + }); + return result; + } catch (err) { + return null; + } + }, + [execute, requester] + ); + + const put = useCallback( + async (url, body, options = {}) => { + try { + const result = await execute(requester, url, { + ...options, + method: "PUT", + headers: { "Content-Type": "application/json", ...options.headers }, + body: JSON.stringify(body), + }); + return result; + } catch (err) { + return null; + } + }, + [execute, requester] + ); + + const del = useCallback( + async (url, options = {}) => { + try { + const result = await execute(requester, url, { + ...options, + method: "DELETE", + }); + return result; + } catch (err) { + return null; + } + }, + [execute, requester] + ); + + return { + ...asyncState, + get, + post, + put, + del, + }; +}; + +export const useGet = (url) => { + const { get, ...fetchState } = useFetch(); + + useEffect(() => { + if (url) get(url); + }, [url, get]); + + return { ...fetchState }; }; diff --git a/src/hooks/I18n.js b/src/hooks/I18n.js index 4c88bef..e417036 100644 --- a/src/hooks/I18n.js +++ b/src/hooks/I18n.js @@ -1,6 +1,6 @@ import { useSetting } from "./Setting"; import { I18N, URL_RAW_PREFIX } from "../config"; -import { useFetch } from "./Fetch"; +import { useGet } from "./Fetch"; export const getI18n = (uiLang, key, defaultText = "") => { return I18N?.[key]?.[uiLang] ?? defaultText; @@ -25,5 +25,5 @@ export const useI18nMd = (key) => { const i18n = useI18n(); const fileName = i18n(key); const url = fileName ? `${URL_RAW_PREFIX}/${fileName}` : ""; - return useFetch(url); + return useGet(url); }; diff --git a/src/index.js b/src/index.js index ade9acc..bdebd72 100644 --- a/src/index.js +++ b/src/index.js @@ -7,14 +7,15 @@ import Paper from "@mui/material/Paper"; import Stack from "@mui/material/Stack"; import Button from "@mui/material/Button"; import Link from "@mui/material/Link"; -import { useFetch } from "./hooks/Fetch"; +import { useGet } from "./hooks/Fetch"; import { I18N, URL_RAW_PREFIX } from "./config"; function App() { const [lang, setLang] = useState("zh"); - const [data, loading, error] = useFetch( + const { data, loading, error } = useGet( `${URL_RAW_PREFIX}/${I18N?.["about_md"]?.[lang]}` ); + return ( @@ -47,7 +48,7 @@ function App() { ) : ( - + )} ); diff --git a/src/views/Options/About.js b/src/views/Options/About.js index aaaaa8d..7112b2b 100644 --- a/src/views/Options/About.js +++ b/src/views/Options/About.js @@ -5,7 +5,7 @@ import { useI18n, useI18nMd } from "../../hooks/I18n"; export default function About() { const i18n = useI18n(); - const [data, loading, error] = useI18nMd("about_md"); + const { data, loading, error } = useI18nMd("about_md"); return ( {loading ? ( diff --git a/src/views/Options/FavWords.js b/src/views/Options/FavWords.js index 94073b6..5200371 100644 --- a/src/views/Options/FavWords.js +++ b/src/views/Options/FavWords.js @@ -21,9 +21,12 @@ import { kissLog } from "../../libs/log"; import { apiTranslate } from "../../apis"; import { OPT_TRANS_BAIDU, PHONIC_MAP } from "../../config"; import { useConfirm } from "../../hooks/Confirm"; +import { useSetting } from "../../hooks/Setting"; function FavAccordion({ word, index }) { const [expanded, setExpanded] = useState(false); + const { setting } = useSetting(); + const { enDict, enSug } = setting?.tranboxSetting || {}; const handleChange = (e) => { setExpanded((pre) => !pre); @@ -40,8 +43,8 @@ function FavAccordion({ word, index }) { {expanded && ( - - + + )} diff --git a/src/views/Options/Playground.js b/src/views/Options/Playground.js index 90207bf..c45cd4d 100644 --- a/src/views/Options/Playground.js +++ b/src/views/Options/Playground.js @@ -8,7 +8,7 @@ export default function Playgound() { const { setting } = useSetting(); const { transApis, langDetector, tranboxSetting } = setting || DEFAULT_SETTING; - const { apiSlugs, fromLang, toLang, toLang2, enDict } = + const { apiSlugs, fromLang, toLang, toLang2, enDict, enSug } = tranboxSetting || DEFAULT_TRANBOX_SETTING; return ( ); diff --git a/src/views/Options/Setting.js b/src/views/Options/Setting.js index e62259e..1195854 100644 --- a/src/views/Options/Setting.js +++ b/src/views/Options/Setting.js @@ -20,6 +20,7 @@ import { OPT_SHORTCUT_SETTING, DEFAULT_BLACKLIST, DEFAULT_CSPLIST, + DEFAULT_ORILIST, MSG_CONTEXT_MENUS, MSG_UPDATE_CSP, DEFAULT_HTTP_TIMEOUT, @@ -79,7 +80,10 @@ export default function Settings() { isExt && sendBgMsg(MSG_CONTEXT_MENUS, value); break; case "csplist": - isExt && sendBgMsg(MSG_UPDATE_CSP, value); + isExt && sendBgMsg(MSG_UPDATE_CSP, { csplist: value }); + break; + case "orilist": + isExt && sendBgMsg(MSG_UPDATE_CSP, { orilist: value }); break; default: } @@ -116,6 +120,7 @@ export default function Settings() { touchTranslate = 2, blacklist = DEFAULT_BLACKLIST.join(",\n"), csplist = DEFAULT_CSPLIST.join(",\n"), + orilist = DEFAULT_ORILIST.join(",\n"), transInterval = 100, langDetector = "-", preInit = true, @@ -399,6 +404,15 @@ export default function Settings() { onChange={handleChange} multiline /> + ) : ( <> diff --git a/src/views/Options/Tranbox.js b/src/views/Options/Tranbox.js index 6a322fa..e2a7390 100644 --- a/src/views/Options/Tranbox.js +++ b/src/views/Options/Tranbox.js @@ -11,6 +11,8 @@ import { OPT_TRANBOX_TRIGGER_ALL, OPT_DICT_BAIDU, OPT_DICT_ALL, + OPT_SUG_ALL, + OPT_SUG_BAIDU, } from "../../config"; import ShortcutInput from "./ShortcutInput"; import FormControlLabel from "@mui/material/FormControlLabel"; @@ -68,6 +70,7 @@ export default function Tranbox() { triggerMode = OPT_TRANBOX_TRIGGER_CLICK, // extStyles = "", enDict = OPT_DICT_BAIDU, + enSug = OPT_SUG_BAIDU, } = tranboxSetting; return ( @@ -89,7 +92,7 @@ export default function Tranbox() { - + + + + {i18n("disable")} + {OPT_SUG_ALL.map((item) => ( + + {item} + + ))} + + { + // if (!data) { + // return; + // } - useEffect(() => { - (async () => { - try { - setLoading(true); - setError(""); - setDictResult(null); + // const copyText = [ + // data.src, + // data.voice + // ?.map(Object.entries) + // .map((item) => item[0]) + // .map(([key, val]) => `${PHONIC_MAP[key]?.[0] || key} ${val}`) + // .join(" "), + // data.content[0].mean + // .map(({ pre, cont }) => { + // return `${pre ? `[${pre}] ` : ""}${Object.keys(cont).join("; ")}`; + // }) + // .join("\n"), + // ].join("\n"); - // if (!isValidWord(text)) { - // return; - // } + // setCopyText(copyText); + // }, [data, setCopyText]); - // // todo: 修复 - // const dictRes = await apiTranslate({ - // text, - // apiSlug: OPT_TRANS_BAIDU, - // fromLang: "en", - // toLang: "zh-CN", - // }); - - // if (dictRes[2]?.type === 1) { - // setDictResult(JSON.parse(dictRes[2].result)); - // } - } catch (err) { - setError(err.message); - } finally { - setLoading(false); - } - })(); - }, [text]); - - useEffect(() => { - if (!dictResult) { - return; - } - - const copyText = [ - dictResult.src, - dictResult.voice - ?.map(Object.entries) - .map((item) => item[0]) - .map(([key, val]) => `${PHONIC_MAP[key]?.[0] || key} ${val}`) - .join(" "), - dictResult.content[0].mean - .map(({ pre, cont }) => { - return `${pre ? `[${pre}] ` : ""}${Object.keys(cont).join("; ")}`; - }) - .join("\n"), - ].join("\n"); - - setCopyText(copyText); - }, [dictResult, setCopyText]); - - if (loading) { - return ; - } - - if (error) { - return {error}; - } - - return baidu: {text}; + return baidu dict not supported yet; { /* {dictResult && ( @@ -109,11 +68,56 @@ function DictBaidu({ text, setCopyText }) { } } +function DictYoudao({ text, setCopyText }) { + const { loading, error, data } = useAsyncNow(apiYoudaoDict, text); + + useEffect(() => { + if (!data) { + return; + } + + const copyText = [ + text, + data?.ec?.word?.trs + ?.map(({ pos, tran }) => `${pos ? `[${pos}] ` : ""}${tran}`) + .join("\n"), + ].join("\n"); + + setCopyText(copyText); + }, [data, setCopyText]); + + if (loading) { + return ; + } + + if (error) { + return {error}; + } + + if (!data) { + return; + } + + return ( + + + {data?.ec?.word?.trs?.map(({ pos, tran }, idx) => ( + + {pos && `[${pos}] `} + {tran} + + ))} + + + ); +} + export default function DictCont({ text, enDict }) { const [copyText, setCopyText] = useState(text); const dictMap = { - [OPT_TRANS_BAIDU]: , + [OPT_DICT_BAIDU]: , + [OPT_DICT_YOUDAO]: , }; return ( @@ -130,6 +134,8 @@ export default function DictCont({ text, enDict }) { )} + + {dictMap[enDict] || Dict not support} ); diff --git a/src/views/Selection/SugCont.js b/src/views/Selection/SugCont.js index 1afe01f..c284d9f 100644 --- a/src/views/Selection/SugCont.js +++ b/src/views/Selection/SugCont.js @@ -1,28 +1,30 @@ -import { useState, useEffect } from "react"; import Typography from "@mui/material/Typography"; -import { apiBaiduSuggest } from "../../apis"; +import CircularProgress from "@mui/material/CircularProgress"; +import Divider from "@mui/material/Divider"; +import Alert from "@mui/material/Alert"; +import { apiBaiduSuggest, apiYoudaoSuggest } from "../../apis"; import Stack from "@mui/material/Stack"; +import { OPT_SUG_BAIDU, OPT_SUG_YOUDAO } from "../../config"; +import { useAsyncNow } from "../../hooks/Fetch"; -export default function SugCont({ text }) { - const [sugs, setSugs] = useState([]); +function SugBaidu({ text }) { + const { loading, error, data } = useAsyncNow(apiBaiduSuggest, text); - useEffect(() => { - (async () => { - try { - setSugs(await apiBaiduSuggest(text)); - } catch (err) { - // skip - } - })(); - }, [text]); + if (loading) { + return ; + } - if (sugs.length === 0) { - return; + if (error) { + return {error}; + } + + if (!data) { + return null; } return ( - - {sugs.map(({ k, v }) => ( + <> + {data.map(({ k, v }) => ( {k} @@ -30,6 +32,49 @@ export default function SugCont({ text }) { ))} + + ); +} + +function SugYoudao({ text }) { + const { loading, error, data } = useAsyncNow(apiYoudaoSuggest, text); + + if (loading) { + return ; + } + + if (error) { + return {error}; + } + + if (!data) { + return null; + } + + return ( + <> + {data.map(({ entry, explain }) => ( + + {entry} + + {explain} + + + ))} + + ); +} + +export default function SugCont({ text, enSug }) { + const sugMap = { + [OPT_SUG_BAIDU]: , + [OPT_SUG_YOUDAO]: , + }; + + return ( + + + {sugMap[enSug] || Sug not support} ); } diff --git a/src/views/Selection/TranBox.js b/src/views/Selection/TranBox.js index 996fe4b..2b929c8 100644 --- a/src/views/Selection/TranBox.js +++ b/src/views/Selection/TranBox.js @@ -114,7 +114,7 @@ export default function TranBox({ text, setText, setShowBox, - tranboxSetting: { apiSlugs, fromLang, toLang, toLang2 }, + tranboxSetting: { enDict, enSug, apiSlugs, fromLang, toLang, toLang2 }, transApis, boxSize, setBoxSize, @@ -128,7 +128,6 @@ export default function TranBox({ setFollowSelection, extStyles = "", langDetector, - enDict, }) { const [mouseHover, setMouseHover] = useState(false); // todo: 这里的 SettingProvider 不应和 background 的共用 @@ -168,6 +167,7 @@ export default function TranBox({ simpleStyle={simpleStyle} langDetector={langDetector} enDict={enDict} + enSug={enSug} /> diff --git a/src/views/Selection/TranForm.js b/src/views/Selection/TranForm.js index d6f7aae..db7d28a 100644 --- a/src/views/Selection/TranForm.js +++ b/src/views/Selection/TranForm.js @@ -12,8 +12,10 @@ import { OPT_LANGS_TO, OPT_LANGDETECTOR_ALL, OPT_DICT_ALL, + OPT_SUG_ALL, OPT_LANGS_MAP, OPT_DICT_MAP, + OPT_SUG_MAP, } from "../../config"; import { useState, useMemo, useEffect } from "react"; import TranCont from "./TranCont"; @@ -30,11 +32,12 @@ export default function TranForm({ apiSlugs: initApiSlugs, fromLang: initFromLang, toLang: initToLang, - toLang2, + toLang2: initToLang2, transApis, simpleStyle = false, langDetector: initLangDetector = "-", enDict: initEnDict = "-", + enSug: initEnSug = "-", isPlaygound = false, }) { const i18n = useI18n(); @@ -44,11 +47,19 @@ export default function TranForm({ const [apiSlugs, setApiSlugs] = useState(initApiSlugs); const [fromLang, setFromLang] = useState(initFromLang); const [toLang, setToLang] = useState(initToLang); + const [toLang2, setToLang2] = useState(initToLang2); const [langDetector, setLangDetector] = useState(initLangDetector); const [enDict, setEnDict] = useState(initEnDict); + const [enSug, setEnSug] = useState(initEnSug); const [deLang, setDeLang] = useState(""); const [deLoading, setDeLoading] = useState(false); + useEffect(() => { + if (!editMode) { + setEditText(text); + } + }, [text, editMode]); + useEffect(() => { if (!text.trim()) { setDeLang(""); @@ -96,6 +107,7 @@ export default function TranForm({ ); const isWord = useMemo(() => isValidWord(text), [text]); + const xs = useMemo(() => (isPlaygound ? 3 : 4), [isPlaygound]); return ( @@ -103,18 +115,18 @@ export default function TranForm({ <> - + { setApiSlugs(e.target.value); }} @@ -126,10 +138,10 @@ export default function TranForm({ ))} - + - + + + {isPlaygound && ( + <> + + { + setToLang2(e.target.value); + }} + > + {OPT_LANGS_TO.map(([lang, name]) => ( + + {name} + + ))} + + + + { + setEnDict(e.target.value); + }} + > + {i18n("disable")} + {OPT_DICT_ALL.map((item) => ( + + {item} + + ))} + + + + { + setEnSug(e.target.value); + }} + > + {i18n("disable")} + {OPT_SUG_ALL.map((item) => ( + + {item} + + ))} + + + + { + setLangDetector(e.target.value); + }} + > + {i18n("disable")} + {OPT_LANGDETECTOR_ALL.map((item) => ( + + {item} + + ))} + + + + + ) : null, + }} + /> + + + )} - {isPlaygound && ( - - - - { - setEnDict(e.target.value); - }} - > - {i18n("disable")} - {OPT_DICT_ALL.map((item) => ( - - {item} - - ))} - - - - { - setLangDetector(e.target.value); - }} - > - {i18n("disable")} - {OPT_LANGDETECTOR_ALL.map((item) => ( - - {item} - - ))} - - - - - ) : null, - }} - /> - - - - )} - { e.stopPropagation(); + setEditMode(false); + setText(editText.trim()); }} > @@ -295,10 +356,11 @@ export default function TranForm({ ))} {isWord && OPT_DICT_MAP.has(enDict) && ( - <> - - - + + )} + + {isWord && OPT_SUG_MAP.has(enSug) && ( + )} ); diff --git a/src/views/Selection/index.js b/src/views/Selection/index.js index 5eb6ee7..c3d0bef 100644 --- a/src/views/Selection/index.js +++ b/src/views/Selection/index.js @@ -10,7 +10,6 @@ import { OPT_TRANBOX_TRIGGER_CLICK, OPT_TRANBOX_TRIGGER_HOVER, OPT_TRANBOX_TRIGGER_SELECT, - OPT_DICT_BAIDU, } from "../../config"; import { isMobile } from "../../libs/mobile"; import { kissLog } from "../../libs/log"; @@ -35,7 +34,6 @@ export default function Slection({ btnOffsetY, boxOffsetX = 0, boxOffsetY = 10, - enDict = OPT_DICT_BAIDU, } = tranboxSetting; const boxWidth = @@ -238,7 +236,6 @@ export default function Slection({ setFollowSelection={setFollowSelection} // extStyles={extStyles} langDetector={langDetector} - enDict={enDict} /> )}