Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2650e5cf7c | ||
|
|
da1fa3e8ed | ||
|
|
e256314d4f | ||
|
|
f8f7d5955f | ||
|
|
fa6f68fec3 | ||
|
|
0705e8a65a | ||
|
|
9d9bbd3821 | ||
|
|
86a312ea6b | ||
|
|
e4771e795b | ||
|
|
1504830142 | ||
|
|
7e99bc7aad | ||
|
|
abca8cb26d |
2
.env
2
.env
@@ -2,7 +2,7 @@ GENERATE_SOURCEMAP=false
|
|||||||
|
|
||||||
REACT_APP_NAME=KISS Translator
|
REACT_APP_NAME=KISS Translator
|
||||||
REACT_APP_NAME_CN=简约翻译
|
REACT_APP_NAME_CN=简约翻译
|
||||||
REACT_APP_VERSION=2.0.11
|
REACT_APP_VERSION=2.0.12
|
||||||
|
|
||||||
REACT_APP_HOMEPAGE=https://github.com/fishjar/kiss-translator
|
REACT_APP_HOMEPAGE=https://github.com/fishjar/kiss-translator
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "kiss-translator",
|
"name": "kiss-translator",
|
||||||
"description": "A minimalist bilingual translation Extension & Greasemonkey Script",
|
"description": "A minimalist bilingual translation Extension & Greasemonkey Script",
|
||||||
"version": "2.0.11",
|
"version": "2.0.12",
|
||||||
"author": "Gabe<yugang2002@gmail.com>",
|
"author": "Gabe<yugang2002@gmail.com>",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -4,321 +4,11 @@
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>%REACT_APP_NAME%</title>
|
<title>%REACT_APP_NAME%</title>
|
||||||
<style>
|
|
||||||
img {
|
|
||||||
width: 1.2em;
|
|
||||||
height: 1.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
svg {
|
|
||||||
max-width: 1.2em;
|
|
||||||
max-height: 1.2em;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<script>
|
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
|
||||||
// (() => {
|
|
||||||
// var shadow = document.querySelector("#shadow1");
|
|
||||||
// var root = shadow.attachShadow({ mode: "open" });
|
|
||||||
// var newLine = document.createElement("p");
|
|
||||||
// newLine.innerText = "new line";
|
|
||||||
// root.appendChild(newLine);
|
|
||||||
// })();
|
|
||||||
|
|
||||||
// setTimeout(function () {
|
|
||||||
// var shadow = document.querySelector("#shadow2");
|
|
||||||
// var root = shadow.attachShadow({ mode: "open" });
|
|
||||||
// }, 1000);
|
|
||||||
|
|
||||||
// setTimeout(() => {
|
|
||||||
// var newLine = document.createElement("p");
|
|
||||||
// newLine.innerText = "new line";
|
|
||||||
// var shadow = document.querySelector("#shadow2");
|
|
||||||
// shadow.shadowRoot.appendChild(newLine);
|
|
||||||
// }, 2000);
|
|
||||||
|
|
||||||
// setTimeout(() => {
|
|
||||||
// var newLine = document.createElement("div");
|
|
||||||
// newLine.innerHTML = "<p>second line</p><p>third line</p>";
|
|
||||||
// var shadow = document.querySelector("#shadow2");
|
|
||||||
// shadow.shadowRoot.appendChild(newLine);
|
|
||||||
// }, 3000);
|
|
||||||
|
|
||||||
// setTimeout(function () {
|
|
||||||
// var el = document.querySelector("h2");
|
|
||||||
// el.innerText = "hello world";
|
|
||||||
|
|
||||||
// var title = document.querySelector("#addtitle");
|
|
||||||
// title.innerHTML =
|
|
||||||
// "<div><p>second title</p><ul><li>second title</li><li><p>second title</p></li></ul></div>";
|
|
||||||
// }, 1000);
|
|
||||||
|
|
||||||
setTimeout(function () {
|
|
||||||
var el = document.querySelector('h2>p>span');
|
|
||||||
el.innerText = 'hello world';
|
|
||||||
}, 1000);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
<div id="root">
|
<div id="root">
|
||||||
<p>You need to enable <code>JavaScript</code> to run <span>this app.</span></p>
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<div id="content">
|
|
||||||
<p>You need to enable JavaScript to run <span>this app.</span></p>
|
|
||||||
The <span>embargo</span> has just lifted to confirm that AmpereOne is coming to
|
|
||||||
Google Cloud with the C3A instances.
|
|
||||||
<br />
|
|
||||||
But these upcoming instances for now are only in private preview form.
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
Needless to say I also haven't had any AmpereOne access to check out the
|
|
||||||
performance and power efficiency of these new Arm server processors from Ampere
|
|
||||||
Computing.
|
|
||||||
<br />
|
|
||||||
</div>
|
|
||||||
<h2>
|
|
||||||
<p>
|
|
||||||
<span>React is a JavaScript library for building user interfaces.</span>
|
|
||||||
</p>
|
|
||||||
</h2>
|
|
||||||
<hr />
|
|
||||||
<input id="input1" style="width: 80%" />
|
|
||||||
<hr />
|
|
||||||
<textarea id="textarea1" style="width: 80%">test</textarea>
|
|
||||||
<hr />
|
|
||||||
<div id="addtitle"></div>
|
|
||||||
<h2>Shadow 1</h2>
|
|
||||||
<div id="shadow1"></div>
|
|
||||||
<h2>Shadow 2</h2>
|
|
||||||
<div id="shadow2"></div>
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<h2>
|
|
||||||
React Server Components (or RSC) is a new application architecture designed by the
|
|
||||||
React team.
|
|
||||||
</h2>
|
|
||||||
<iframe
|
|
||||||
id="iframe1"
|
|
||||||
width="800px"
|
|
||||||
height="600px"
|
|
||||||
src="http://localhost:3000/index.html"></iframe>
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<h2>We’ve first shared our research on RSC in an introductory talk and an RFC.</h2>
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<h2>
|
|
||||||
To recap them, we are introducing a new kind of component—Server Components—that
|
|
||||||
run ahead of time and are excluded from your JavaScript bundle.
|
|
||||||
</h2>
|
|
||||||
<iframe id="iframe2" width="800px" height="600px" src="https://react.dev/"></iframe>
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<div class="cont cont1">
|
|
||||||
<h2>
|
|
||||||
Server Components can run during the build, letting you read from the filesystem
|
|
||||||
or fetch static content.
|
|
||||||
</h2>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
They can also run on the server, letting you access your data layer without
|
|
||||||
having to build an API. You can pass data by props from Server Components to
|
|
||||||
the interactive Client Components in the browser.
|
|
||||||
</li>
|
|
||||||
<li>以声明式编写 UI,可以让你的代码更加可靠,且方便调试。</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<div class="cont cont2">
|
|
||||||
<h2>
|
|
||||||
Since our last update, we have merged the React Server Components RFC to ratify
|
|
||||||
the proposal.
|
|
||||||
</h2>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
RSC combines the simple “request/response” mental model of server-centric
|
|
||||||
Multi-Page Apps with the seamless interactivity of client-centric Single-Page
|
|
||||||
Apps, giving you the best of both worlds.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
React 使创建交互式 UI
|
|
||||||
变得轻而易举。为你应用的每一个状态设计简洁的视图,当数据变动时 React
|
|
||||||
能高效更新并渲染合适的组件。
|
|
||||||
</li>
|
|
||||||
<li>以声明式编写 UI,可以让你的代码更加可靠,且方便调试。</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<!--
|
<!--
|
||||||
This HTML file is a template.
|
This HTML file is a template.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"name": "__MSG_app_name__",
|
"name": "__MSG_app_name__",
|
||||||
"description": "__MSG_app_description__",
|
"description": "__MSG_app_description__",
|
||||||
"version": "2.0.11",
|
"version": "2.0.12",
|
||||||
"default_locale": "en",
|
"default_locale": "en",
|
||||||
"author": "Gabe<yugang2002@gmail.com>",
|
"author": "Gabe<yugang2002@gmail.com>",
|
||||||
"homepage_url": "https://github.com/fishjar/kiss-translator",
|
"homepage_url": "https://github.com/fishjar/kiss-translator",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"name": "__MSG_app_name__",
|
"name": "__MSG_app_name__",
|
||||||
"description": "__MSG_app_description__",
|
"description": "__MSG_app_description__",
|
||||||
"version": "2.0.11",
|
"version": "2.0.12",
|
||||||
"default_locale": "en",
|
"default_locale": "en",
|
||||||
"author": "Gabe<yugang2002@gmail.com>",
|
"author": "Gabe<yugang2002@gmail.com>",
|
||||||
"homepage_url": "https://github.com/fishjar/kiss-translator",
|
"homepage_url": "https://github.com/fishjar/kiss-translator",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"name": "__MSG_app_name__",
|
"name": "__MSG_app_name__",
|
||||||
"description": "__MSG_app_description__",
|
"description": "__MSG_app_description__",
|
||||||
"version": "2.0.11",
|
"version": "2.0.12",
|
||||||
"default_locale": "en",
|
"default_locale": "en",
|
||||||
"author": "Gabe<yugang2002@gmail.com>",
|
"author": "Gabe<yugang2002@gmail.com>",
|
||||||
"homepage_url": "https://github.com/fishjar/kiss-translator",
|
"homepage_url": "https://github.com/fishjar/kiss-translator",
|
||||||
|
|||||||
@@ -739,6 +739,13 @@ export const genTransReq = async ({ reqHook, ...args }) => {
|
|||||||
// 执行 request hook
|
// 执行 request hook
|
||||||
if (reqHook?.trim() && !events) {
|
if (reqHook?.trim() && !events) {
|
||||||
try {
|
try {
|
||||||
|
const req = {
|
||||||
|
url,
|
||||||
|
body,
|
||||||
|
headers,
|
||||||
|
userMsg,
|
||||||
|
method,
|
||||||
|
};
|
||||||
interpreter.run(`exports.reqHook = ${reqHook}`);
|
interpreter.run(`exports.reqHook = ${reqHook}`);
|
||||||
const hookResult = await interpreter.exports.reqHook(
|
const hookResult = await interpreter.exports.reqHook(
|
||||||
{
|
{
|
||||||
@@ -747,20 +754,16 @@ export const genTransReq = async ({ reqHook, ...args }) => {
|
|||||||
defaultSubtitlePrompt,
|
defaultSubtitlePrompt,
|
||||||
defaultNobatchPrompt,
|
defaultNobatchPrompt,
|
||||||
defaultNobatchUserPrompt,
|
defaultNobatchUserPrompt,
|
||||||
|
req,
|
||||||
},
|
},
|
||||||
{
|
req
|
||||||
url,
|
|
||||||
body,
|
|
||||||
headers,
|
|
||||||
userMsg,
|
|
||||||
method,
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
if (hookResult && hookResult.url) {
|
if (hookResult && hookResult.url) {
|
||||||
return genInit(hookResult);
|
return genInit(hookResult);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
kissLog("run req hook", err);
|
kissLog("run req hook", err);
|
||||||
|
throw new Error(`Request hook error: ${err.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -817,6 +820,7 @@ export const parseTransRes = async (
|
|||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
kissLog("run res hook", err);
|
kissLog("run res hook", err);
|
||||||
|
throw new Error(`Response hook error: ${err.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ import {
|
|||||||
MSG_UPDATE_CSP,
|
MSG_UPDATE_CSP,
|
||||||
MSG_BUILTINAI_DETECT,
|
MSG_BUILTINAI_DETECT,
|
||||||
MSG_BUILTINAI_TRANSLATE,
|
MSG_BUILTINAI_TRANSLATE,
|
||||||
DEFAULT_CSPLIST,
|
|
||||||
DEFAULT_ORILIST,
|
|
||||||
CMD_TOGGLE_TRANSLATE,
|
CMD_TOGGLE_TRANSLATE,
|
||||||
CMD_TOGGLE_STYLE,
|
CMD_TOGGLE_STYLE,
|
||||||
CMD_OPEN_OPTIONS,
|
CMD_OPEN_OPTIONS,
|
||||||
@@ -37,7 +35,7 @@ import { injectInlineJsBg, injectInternalCss } from "./libs/injector";
|
|||||||
import { kissLog, logger } from "./libs/log";
|
import { kissLog, logger } from "./libs/log";
|
||||||
import { chromeDetect, chromeTranslate } from "./libs/builtinAI";
|
import { chromeDetect, chromeTranslate } from "./libs/builtinAI";
|
||||||
|
|
||||||
globalThis.ContextType = "BACKGROUND";
|
globalThis.__KISS_CONTEXT__ = "background";
|
||||||
|
|
||||||
const CSP_RULE_START_ID = 1;
|
const CSP_RULE_START_ID = 1;
|
||||||
const ORI_RULE_START_ID = 10000;
|
const ORI_RULE_START_ID = 10000;
|
||||||
@@ -193,19 +191,21 @@ async function registerMsgDisplayScript() {
|
|||||||
/**
|
/**
|
||||||
* 插件安装
|
* 插件安装
|
||||||
*/
|
*/
|
||||||
browser.runtime.onInstalled.addListener(() => {
|
browser.runtime.onInstalled.addListener(async () => {
|
||||||
tryInitDefaultData();
|
await tryInitDefaultData();
|
||||||
|
|
||||||
//在thunderbird中注册脚本
|
//在thunderbird中注册脚本
|
||||||
if (process.env.REACT_APP_CLIENT === CLIENT_THUNDERBIRD) {
|
if (process.env.REACT_APP_CLIENT === CLIENT_THUNDERBIRD) {
|
||||||
registerMsgDisplayScript();
|
registerMsgDisplayScript();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { contextMenuType, csplist, orilist } = await getSettingWithDefault();
|
||||||
|
|
||||||
// 右键菜单
|
// 右键菜单
|
||||||
addContextMenus();
|
addContextMenus(contextMenuType);
|
||||||
|
|
||||||
// 禁用CSP
|
// 禁用CSP
|
||||||
updateCspRules({ csplist: DEFAULT_CSPLIST, orilist: DEFAULT_ORILIST });
|
updateCspRules({ csplist, orilist });
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ export const SHADOW_KEY = ">>>";
|
|||||||
export const DEFAULT_COLOR = "#209CEE"; // 默认高亮背景色/线条颜色
|
export const DEFAULT_COLOR = "#209CEE"; // 默认高亮背景色/线条颜色
|
||||||
|
|
||||||
export const DEFAULT_TRANS_TAG = "font";
|
export const DEFAULT_TRANS_TAG = "font";
|
||||||
// export const DEFAULT_SELECT_STYLE =
|
export const DEFAULT_SELECT_STYLE =
|
||||||
// "-webkit-line-clamp: unset; max-height: none; height: auto;";
|
"-webkit-line-clamp: unset; max-height: none; height: auto;";
|
||||||
|
|
||||||
export const OPT_TIMING_PAGESCROLL = "mk_pagescroll"; // 滚动加载翻译
|
export const OPT_TIMING_PAGESCROLL = "mk_pagescroll"; // 滚动加载翻译
|
||||||
export const OPT_TIMING_PAGEOPEN = "mk_pageopen"; // 直接翻译到底
|
export const OPT_TIMING_PAGEOPEN = "mk_pageopen"; // 直接翻译到底
|
||||||
@@ -108,7 +108,7 @@ export const GLOBLA_RULE = {
|
|||||||
textExtStyle: "", // 译文附加样式
|
textExtStyle: "", // 译文附加样式
|
||||||
termsStyle: "font-weight: bold;", // 专业术语样式
|
termsStyle: "font-weight: bold;", // 专业术语样式
|
||||||
highlightStyle: "color: red;", // 高亮词汇样式
|
highlightStyle: "color: red;", // 高亮词汇样式
|
||||||
selectStyle: "", // 选择器节点样式
|
selectStyle: DEFAULT_SELECT_STYLE, // 选择器节点样式
|
||||||
parentStyle: "", // 选择器父节点样式
|
parentStyle: "", // 选择器父节点样式
|
||||||
grandStyle: "", // 选择器祖节点样式
|
grandStyle: "", // 选择器祖节点样式
|
||||||
injectJs: "", // 注入JS
|
injectJs: "", // 注入JS
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
import { run } from "./common";
|
import { run } from "./common";
|
||||||
|
|
||||||
|
globalThis.__KISS_CONTEXT__ = "content";
|
||||||
|
|
||||||
run();
|
run();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { apiBaiduTTS } from "../apis";
|
import { logger } from "../libs/log";
|
||||||
import { kissLog } from "../libs/log";
|
import { fetchData } from "../libs/fetch";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 声音播放hook
|
* 声音播放hook
|
||||||
@@ -12,50 +12,97 @@ export function useAudio(src) {
|
|||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
const [ready, setReady] = useState(false);
|
const [ready, setReady] = useState(false);
|
||||||
const [playing, setPlaying] = useState(false);
|
const [playing, setPlaying] = useState(false);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const onPlay = useCallback(() => {
|
const onPlay = useCallback(async () => {
|
||||||
audioRef.current?.play();
|
if (!audioRef.current) return;
|
||||||
|
try {
|
||||||
|
await audioRef.current.play();
|
||||||
|
} catch (err) {
|
||||||
|
logger.info("Playback failed:", err);
|
||||||
|
setPlaying(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onPause = useCallback(() => {
|
||||||
|
audioRef.current?.pause();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!src) {
|
if (!src) return;
|
||||||
return;
|
|
||||||
}
|
let ignore = false;
|
||||||
const audio = new Audio(src);
|
let objectUrl = null;
|
||||||
audio.addEventListener("error", (err) => setError(err));
|
|
||||||
audio.addEventListener("canplaythrough", () => setReady(true));
|
setReady(false);
|
||||||
audio.addEventListener("play", () => setPlaying(true));
|
setError(null);
|
||||||
audio.addEventListener("ended", () => setPlaying(false));
|
setPlaying(false);
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
const audio = new Audio();
|
||||||
audioRef.current = audio;
|
audioRef.current = audio;
|
||||||
|
|
||||||
|
const handleCanPlay = () => setReady(true);
|
||||||
|
const handlePlay = () => setPlaying(true);
|
||||||
|
const handlePause = () => setPlaying(false);
|
||||||
|
const handleEnded = () => setPlaying(false);
|
||||||
|
const handleError = (e) => {
|
||||||
|
if (!ignore) {
|
||||||
|
setError(audio.error || e);
|
||||||
|
setReady(false);
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
audio.addEventListener("canplaythrough", handleCanPlay);
|
||||||
|
audio.addEventListener("play", handlePlay);
|
||||||
|
audio.addEventListener("pause", handlePause);
|
||||||
|
audio.addEventListener("ended", handleEnded);
|
||||||
|
audio.addEventListener("error", handleError);
|
||||||
|
|
||||||
|
const loadAudio = async () => {
|
||||||
|
try {
|
||||||
|
const data = await fetchData(src, {}, { expect: "audio" });
|
||||||
|
if (ignore) return;
|
||||||
|
|
||||||
|
audio.src = data;
|
||||||
|
|
||||||
|
setLoading(false);
|
||||||
|
} catch (err) {
|
||||||
|
if (!ignore) {
|
||||||
|
logger.info("Audio fetch failed:", err);
|
||||||
|
setError(err);
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadAudio();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
ignore = true;
|
||||||
|
|
||||||
|
audio.pause();
|
||||||
|
audio.removeAttribute("src");
|
||||||
|
|
||||||
|
if (objectUrl) {
|
||||||
|
URL.revokeObjectURL(objectUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
audio.removeEventListener("canplaythrough", handleCanPlay);
|
||||||
|
audio.removeEventListener("play", handlePlay);
|
||||||
|
audio.removeEventListener("pause", handlePause);
|
||||||
|
audio.removeEventListener("ended", handleEnded);
|
||||||
|
audio.removeEventListener("error", handleError);
|
||||||
|
};
|
||||||
}, [src]);
|
}, [src]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
loading,
|
||||||
error,
|
error,
|
||||||
ready,
|
ready,
|
||||||
playing,
|
playing,
|
||||||
onPlay,
|
onPlay,
|
||||||
|
onPause,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取语音hook
|
|
||||||
* @param {*} text
|
|
||||||
* @param {*} lan
|
|
||||||
* @param {*} spd
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export function useTextAudio(text, lan = "uk", spd = 3) {
|
|
||||||
const [src, setSrc] = useState("");
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
(async () => {
|
|
||||||
try {
|
|
||||||
setSrc(await apiBaiduTTS(text, lan, spd));
|
|
||||||
} catch (err) {
|
|
||||||
kissLog("baidu tts", err);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
}, [text, lan, spd]);
|
|
||||||
|
|
||||||
return useAudio(src);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -25,17 +25,15 @@ const SettingContext = createContext({
|
|||||||
reloadSetting: () => {},
|
reloadSetting: () => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
export function SettingProvider({ children, isSettingPage }) {
|
export function SettingProvider({ children, context }) {
|
||||||
|
const isOptionsPage = useMemo(() => context === "options", [context]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: setting,
|
data: setting,
|
||||||
isLoading,
|
isLoading,
|
||||||
update,
|
update,
|
||||||
reload,
|
reload,
|
||||||
} = useStorage(
|
} = useStorage(STOKEY_SETTING, DEFAULT_SETTING, KV_SETTING_KEY);
|
||||||
STOKEY_SETTING,
|
|
||||||
DEFAULT_SETTING,
|
|
||||||
isSettingPage ? KV_SETTING_KEY : ""
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (typeof setting?.darkMode === "boolean") {
|
if (typeof setting?.darkMode === "boolean") {
|
||||||
@@ -47,7 +45,7 @@ export function SettingProvider({ children, isSettingPage }) {
|
|||||||
}, [setting?.darkMode, update]);
|
}, [setting?.darkMode, update]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isSettingPage) return;
|
if (!isOptionsPage) return;
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
@@ -59,7 +57,7 @@ export function SettingProvider({ children, isSettingPage }) {
|
|||||||
logger.error("Failed to fetch log level, using default.", error);
|
logger.error("Failed to fetch log level, using default.", error);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}, [isSettingPage, setting?.logLevel]);
|
}, [isOptionsPage, setting?.logLevel]);
|
||||||
|
|
||||||
const updateSetting = useCallback(
|
const updateSetting = useCallback(
|
||||||
(objOrFn) => {
|
(objOrFn) => {
|
||||||
@@ -81,19 +79,21 @@ export function SettingProvider({ children, isSettingPage }) {
|
|||||||
|
|
||||||
const value = useMemo(
|
const value = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
|
context,
|
||||||
setting,
|
setting,
|
||||||
updateSetting,
|
updateSetting,
|
||||||
updateChild,
|
updateChild,
|
||||||
reloadSetting: reload,
|
reloadSetting: reload,
|
||||||
}),
|
}),
|
||||||
[setting, updateSetting, updateChild, reload]
|
[context, setting, updateSetting, updateChild, reload]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <Loading />;
|
return isOptionsPage ? <Loading /> : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!setting) {
|
if (!setting) {
|
||||||
|
return isOptionsPage ? (
|
||||||
<center>
|
<center>
|
||||||
<Alert severity="error" sx={{ maxWidth: 600, margin: "60px auto" }}>
|
<Alert severity="error" sx={{ maxWidth: 600, margin: "60px auto" }}>
|
||||||
<p>数据加载出错,请刷新页面或卸载后重新安装。</p>
|
<p>数据加载出错,请刷新页面或卸载后重新安装。</p>
|
||||||
@@ -102,7 +102,8 @@ export function SettingProvider({ children, isSettingPage }) {
|
|||||||
reinstall.
|
reinstall.
|
||||||
</p>
|
</p>
|
||||||
</Alert>
|
</Alert>
|
||||||
</center>;
|
</center>
|
||||||
|
) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { storage } from "../libs/storage";
|
|||||||
import { kissLog } from "../libs/log";
|
import { kissLog } from "../libs/log";
|
||||||
import { syncData } from "../libs/sync";
|
import { syncData } from "../libs/sync";
|
||||||
import { useDebouncedCallback } from "./DebouncedCallback";
|
import { useDebouncedCallback } from "./DebouncedCallback";
|
||||||
|
import { isOptions } from "../libs/browser";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用于将组件状态与 Storage 同步
|
* 用于将组件状态与 Storage 同步
|
||||||
@@ -79,7 +80,7 @@ export function useStorage(key, defaultVal = null, syncKey = "") {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 触发远端同步
|
// 触发远端同步
|
||||||
if (syncKey) {
|
if (syncKey && isOptions()) {
|
||||||
debouncedSync(syncKey, data);
|
debouncedSync(syncKey, data);
|
||||||
}
|
}
|
||||||
}, [key, syncKey, isLoading, data, debouncedSync]);
|
}, [key, syncKey, isLoading, data, debouncedSync]);
|
||||||
|
|||||||
@@ -14,7 +14,30 @@ function _browser() {
|
|||||||
|
|
||||||
export const browser = _browser();
|
export const browser = _browser();
|
||||||
|
|
||||||
export const isBg = () => globalThis?.ContextType === "BACKGROUND";
|
export const getContext = () => {
|
||||||
|
const context = globalThis.__KISS_CONTEXT__;
|
||||||
|
if (context) return context;
|
||||||
|
|
||||||
|
// if (typeof window === "undefined" || typeof document === "undefined") {
|
||||||
|
// return "background";
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const extensionOrigin = browser.runtime.getURL("");
|
||||||
|
// if (!window.location.href.startsWith(extensionOrigin)) {
|
||||||
|
// return "content";
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const pathname = window.location.pathname;
|
||||||
|
// if (pathname.includes("popup")) return "popup";
|
||||||
|
// if (pathname.includes("options")) return "options";
|
||||||
|
// if (pathname.includes("sidepanel")) return "sidepanel";
|
||||||
|
// if (pathname.includes("background")) return "background";
|
||||||
|
|
||||||
|
return "undefined";
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isBg = () => getContext() === "background";
|
||||||
|
export const isOptions = () => getContext() === "options";
|
||||||
|
|
||||||
export const isBuiltinAIAvailable =
|
export const isBuiltinAIAvailable =
|
||||||
"LanguageDetector" in globalThis && "Translator" in globalThis;
|
"LanguageDetector" in globalThis && "Translator" in globalThis;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { blobToBase64 } from "./utils";
|
|||||||
*/
|
*/
|
||||||
export const tryClearCaches = async () => {
|
export const tryClearCaches = async () => {
|
||||||
try {
|
try {
|
||||||
if (isExt && !isBg) {
|
if (isExt && !isBg()) {
|
||||||
await sendBgMsg(MSG_CLEAR_CACHES);
|
await sendBgMsg(MSG_CLEAR_CACHES);
|
||||||
} else {
|
} else {
|
||||||
await caches.delete(CACHE_NAME);
|
await caches.delete(CACHE_NAME);
|
||||||
@@ -50,13 +50,13 @@ const newCacheReq = async (input, init) => {
|
|||||||
* @param {*} init
|
* @param {*} init
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const getHttpCache = async ({ input, init }) => {
|
export const getHttpCache = async ({ input, init, expect }) => {
|
||||||
try {
|
try {
|
||||||
const request = await newCacheReq(input, init);
|
const request = await newCacheReq(input, init);
|
||||||
const cache = await caches.open(CACHE_NAME);
|
const cache = await caches.open(CACHE_NAME);
|
||||||
const response = await cache.match(request);
|
const response = await cache.match(request);
|
||||||
if (response) {
|
if (response) {
|
||||||
const res = await parseResponse(response);
|
const res = await parseResponse(response, expect);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -99,7 +99,7 @@ export const putHttpCache = async ({
|
|||||||
* @param {*} res
|
* @param {*} res
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const parseResponse = async (res) => {
|
export const parseResponse = async (res, expect = null) => {
|
||||||
if (!res) {
|
if (!res) {
|
||||||
throw new Error("Response object does not exist");
|
throw new Error("Response object does not exist");
|
||||||
}
|
}
|
||||||
@@ -108,21 +108,45 @@ export const parseResponse = async (res) => {
|
|||||||
const msg = {
|
const msg = {
|
||||||
url: res.url,
|
url: res.url,
|
||||||
status: res.status,
|
status: res.status,
|
||||||
|
statusText: res.statusText,
|
||||||
};
|
};
|
||||||
if (res.headers.get("Content-Type")?.includes("json")) {
|
|
||||||
msg.response = await res.json();
|
try {
|
||||||
|
const errorText = await res.clone().text();
|
||||||
|
try {
|
||||||
|
msg.response = JSON.parse(errorText);
|
||||||
|
} catch {
|
||||||
|
msg.response = errorText;
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
msg.response = "Unable to read error body";
|
||||||
|
}
|
||||||
|
|
||||||
throw new Error(JSON.stringify(msg));
|
throw new Error(JSON.stringify(msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
const contentType = res.headers.get("Content-Type");
|
const contentType = res.headers.get("Content-Type") || "";
|
||||||
if (contentType?.includes("json")) {
|
if (expect === "blob") return res.blob();
|
||||||
return res.json();
|
if (expect === "text") return res.text();
|
||||||
} else if (contentType?.includes("audio")) {
|
if (expect === "json") return res.json();
|
||||||
|
if (
|
||||||
|
expect === "audio" ||
|
||||||
|
contentType.includes("audio") ||
|
||||||
|
contentType.includes("image") ||
|
||||||
|
contentType.includes("video")
|
||||||
|
) {
|
||||||
const blob = await res.blob();
|
const blob = await res.blob();
|
||||||
return blobToBase64(blob);
|
return blobToBase64(blob);
|
||||||
}
|
}
|
||||||
return res.text();
|
|
||||||
|
const text = await res.text();
|
||||||
|
if (!text) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return JSON.parse(text);
|
||||||
|
} catch (err) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ export const fetchPatcher = async (input, init = {}, opts) => {
|
|||||||
*/
|
*/
|
||||||
export const fetchHandle = async ({ input, init, opts }) => {
|
export const fetchHandle = async ({ input, init, opts }) => {
|
||||||
const res = await fetchPatcher(input, init, opts);
|
const res = await fetchPatcher(input, init, opts);
|
||||||
return parseResponse(res);
|
return parseResponse(res, opts.expect);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -61,14 +61,25 @@ export const shortcutRegister = (targetKeys = [], fn, target = document) => {
|
|||||||
if (targetKeys.length === 0) return () => {};
|
if (targetKeys.length === 0) return () => {};
|
||||||
|
|
||||||
const targetKeySet = new Set(targetKeys);
|
const targetKeySet = new Set(targetKeys);
|
||||||
|
let hasInterference = false;
|
||||||
const onKeyDown = (pressedKeys, event) => {
|
const onKeyDown = (pressedKeys, event) => {
|
||||||
if (isSameSet(targetKeySet, pressedKeys)) {
|
// if (isSameSet(targetKeySet, pressedKeys)) {
|
||||||
// event.preventDefault(); // 阻止浏览器的默认行为
|
// // event.preventDefault(); // 阻止浏览器的默认行为
|
||||||
// event.stopPropagation(); // 阻止事件继续(向父元素)冒泡
|
// // event.stopPropagation(); // 阻止事件继续(向父元素)冒泡
|
||||||
fn();
|
// fn();
|
||||||
|
// }
|
||||||
|
if (!targetKeySet.has(event.code)) {
|
||||||
|
hasInterference = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const onKeyUp = (pressedKeys, event) => {
|
||||||
|
if (isSameSet(targetKeySet, pressedKeys) && !hasInterference) {
|
||||||
|
fn();
|
||||||
|
}
|
||||||
|
if (pressedKeys.size === 1) {
|
||||||
|
hasInterference = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const onKeyUp = () => {};
|
|
||||||
|
|
||||||
return shortcutListener(onKeyDown, onKeyUp, target);
|
return shortcutListener(onKeyDown, onKeyUp, target);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import React from "react";
|
|||||||
import ReactDOM from "react-dom/client";
|
import ReactDOM from "react-dom/client";
|
||||||
import Options from "./views/Options";
|
import Options from "./views/Options";
|
||||||
|
|
||||||
|
globalThis.__KISS_CONTEXT__ = "options";
|
||||||
|
|
||||||
const root = ReactDOM.createRoot(document.getElementById("root"));
|
const root = ReactDOM.createRoot(document.getElementById("root"));
|
||||||
root.render(
|
root.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
|
|||||||
@@ -4,10 +4,12 @@ import { SettingProvider } from "./hooks/Setting";
|
|||||||
import ThemeProvider from "./hooks/Theme";
|
import ThemeProvider from "./hooks/Theme";
|
||||||
import Popup from "./views/Popup";
|
import Popup from "./views/Popup";
|
||||||
|
|
||||||
|
globalThis.__KISS_CONTEXT__ = "popup";
|
||||||
|
|
||||||
const root = ReactDOM.createRoot(document.getElementById("root"));
|
const root = ReactDOM.createRoot(document.getElementById("root"));
|
||||||
root.render(
|
root.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<SettingProvider>
|
<SettingProvider context="popup">
|
||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
<Popup />
|
<Popup />
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export default function ContentFab({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingProvider>
|
<SettingProvider context="fab">
|
||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
<Draggable
|
<Draggable
|
||||||
key="fab"
|
key="fab"
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ export default function Action({ translator, processActions }) {
|
|||||||
}, [windowSize]);
|
}, [windowSize]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingProvider>
|
<SettingProvider context="contentPopup">
|
||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
{showPopup && (
|
{showPopup && (
|
||||||
<Draggable
|
<Draggable
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ export default function Options() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingProvider isSettingPage={true}>
|
<SettingProvider context="options">
|
||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
<AlertProvider>
|
<AlertProvider>
|
||||||
<ConfirmProvider>
|
<ConfirmProvider>
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import IconButton from "@mui/material/IconButton";
|
import IconButton from "@mui/material/IconButton";
|
||||||
import VolumeUpIcon from "@mui/icons-material/VolumeUp";
|
import VolumeUpIcon from "@mui/icons-material/VolumeUp";
|
||||||
import { useAudio } from "../../hooks/Audio";
|
import { useAudio } from "../../hooks/Audio";
|
||||||
|
import queryString from "query-string";
|
||||||
|
|
||||||
export default function AudioBtn({ src }) {
|
export function AudioBtn({ src }) {
|
||||||
const { error, ready, playing, onPlay } = useAudio(src);
|
const { error, ready, playing, onPlay } = useAudio(src);
|
||||||
|
|
||||||
if (error || !ready) {
|
if (error || !ready) {
|
||||||
@@ -27,3 +28,10 @@ export default function AudioBtn({ src }) {
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function BaiduAudioBtn({ text, lan = "uk", spd = 3 }) {
|
||||||
|
if (!text) return null;
|
||||||
|
|
||||||
|
const src = `https://fanyi.baidu.com/gettts?${queryString.stringify({ lan, text, spd })}`;
|
||||||
|
return <AudioBtn src={src} />;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
import AudioBtn from "./AudioBtn";
|
import { AudioBtn, BaiduAudioBtn } from "./AudioBtn";
|
||||||
import { OPT_DICT_BING, OPT_DICT_YOUDAO } from "../../config";
|
import { OPT_DICT_BING, OPT_DICT_YOUDAO } from "../../config";
|
||||||
import { apiMicrosoftDict, apiYoudaoDict } from "../../apis";
|
import { apiMicrosoftDict, apiYoudaoDict } from "../../apis";
|
||||||
|
|
||||||
@@ -48,12 +48,14 @@ export const dictHandlers = {
|
|||||||
style={{ display: "inline-block", paddingRight: "1em" }}
|
style={{ display: "inline-block", paddingRight: "1em" }}
|
||||||
>
|
>
|
||||||
<Typography component="span">{`UK [${data?.ec?.word?.ukphone}]`}</Typography>
|
<Typography component="span">{`UK [${data?.ec?.word?.ukphone}]`}</Typography>
|
||||||
|
<BaiduAudioBtn text={data?.ec?.word?.["return-phrase"]} lan="uk" />
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography
|
<Typography
|
||||||
component="div"
|
component="div"
|
||||||
style={{ display: "inline-block", paddingRight: "1em" }}
|
style={{ display: "inline-block", paddingRight: "1em" }}
|
||||||
>
|
>
|
||||||
<Typography component="span">{`US [${data?.ec?.word?.usphone}]`}</Typography>
|
<Typography component="span">{`US [${data?.ec?.word?.usphone}]`}</Typography>
|
||||||
|
<BaiduAudioBtn text={data?.ec?.word?.["return-phrase"]} lan="en" />
|
||||||
</Typography>
|
</Typography>
|
||||||
</Typography>
|
</Typography>
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ export default function TranBox({
|
|||||||
const [mouseHover, setMouseHover] = useState(false);
|
const [mouseHover, setMouseHover] = useState(false);
|
||||||
// todo: 这里的 SettingProvider 不应和 background 的共用
|
// todo: 这里的 SettingProvider 不应和 background 的共用
|
||||||
return (
|
return (
|
||||||
<SettingProvider>
|
<SettingProvider context="tranbox">
|
||||||
<ThemeProvider styles={extStyles}>
|
<ThemeProvider styles={extStyles}>
|
||||||
{showBox && (
|
{showBox && (
|
||||||
<DraggableResizable
|
<DraggableResizable
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ export default function Slection({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function handleMouseup(e) {
|
async function handleMouseup(e) {
|
||||||
e.stopPropagation();
|
// e.stopPropagation();
|
||||||
await sleep(200);
|
await sleep(200);
|
||||||
|
|
||||||
const selection = window.getSelection();
|
const selection = window.getSelection();
|
||||||
|
|||||||
Reference in New Issue
Block a user