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_CN=简约翻译
|
||||
REACT_APP_VERSION=2.0.11
|
||||
REACT_APP_VERSION=2.0.12
|
||||
|
||||
REACT_APP_HOMEPAGE=https://github.com/fishjar/kiss-translator
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "kiss-translator",
|
||||
"description": "A minimalist bilingual translation Extension & Greasemonkey Script",
|
||||
"version": "2.0.11",
|
||||
"version": "2.0.12",
|
||||
"author": "Gabe<yugang2002@gmail.com>",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
|
||||
@@ -4,321 +4,11 @@
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<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>
|
||||
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<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>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"manifest_version": 2,
|
||||
"name": "__MSG_app_name__",
|
||||
"description": "__MSG_app_description__",
|
||||
"version": "2.0.11",
|
||||
"version": "2.0.12",
|
||||
"default_locale": "en",
|
||||
"author": "Gabe<yugang2002@gmail.com>",
|
||||
"homepage_url": "https://github.com/fishjar/kiss-translator",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"manifest_version": 3,
|
||||
"name": "__MSG_app_name__",
|
||||
"description": "__MSG_app_description__",
|
||||
"version": "2.0.11",
|
||||
"version": "2.0.12",
|
||||
"default_locale": "en",
|
||||
"author": "Gabe<yugang2002@gmail.com>",
|
||||
"homepage_url": "https://github.com/fishjar/kiss-translator",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"manifest_version": 2,
|
||||
"name": "__MSG_app_name__",
|
||||
"description": "__MSG_app_description__",
|
||||
"version": "2.0.11",
|
||||
"version": "2.0.12",
|
||||
"default_locale": "en",
|
||||
"author": "Gabe<yugang2002@gmail.com>",
|
||||
"homepage_url": "https://github.com/fishjar/kiss-translator",
|
||||
|
||||
@@ -739,6 +739,13 @@ export const genTransReq = async ({ reqHook, ...args }) => {
|
||||
// 执行 request hook
|
||||
if (reqHook?.trim() && !events) {
|
||||
try {
|
||||
const req = {
|
||||
url,
|
||||
body,
|
||||
headers,
|
||||
userMsg,
|
||||
method,
|
||||
};
|
||||
interpreter.run(`exports.reqHook = ${reqHook}`);
|
||||
const hookResult = await interpreter.exports.reqHook(
|
||||
{
|
||||
@@ -747,20 +754,16 @@ export const genTransReq = async ({ reqHook, ...args }) => {
|
||||
defaultSubtitlePrompt,
|
||||
defaultNobatchPrompt,
|
||||
defaultNobatchUserPrompt,
|
||||
req,
|
||||
},
|
||||
{
|
||||
url,
|
||||
body,
|
||||
headers,
|
||||
userMsg,
|
||||
method,
|
||||
}
|
||||
req
|
||||
);
|
||||
if (hookResult && hookResult.url) {
|
||||
return genInit(hookResult);
|
||||
}
|
||||
} catch (err) {
|
||||
kissLog("run req hook", err);
|
||||
throw new Error(`Request hook error: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -817,6 +820,7 @@ export const parseTransRes = async (
|
||||
}
|
||||
} catch (err) {
|
||||
kissLog("run res hook", err);
|
||||
throw new Error(`Response hook error: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,8 +15,6 @@ import {
|
||||
MSG_UPDATE_CSP,
|
||||
MSG_BUILTINAI_DETECT,
|
||||
MSG_BUILTINAI_TRANSLATE,
|
||||
DEFAULT_CSPLIST,
|
||||
DEFAULT_ORILIST,
|
||||
CMD_TOGGLE_TRANSLATE,
|
||||
CMD_TOGGLE_STYLE,
|
||||
CMD_OPEN_OPTIONS,
|
||||
@@ -37,7 +35,7 @@ import { injectInlineJsBg, injectInternalCss } from "./libs/injector";
|
||||
import { kissLog, logger } from "./libs/log";
|
||||
import { chromeDetect, chromeTranslate } from "./libs/builtinAI";
|
||||
|
||||
globalThis.ContextType = "BACKGROUND";
|
||||
globalThis.__KISS_CONTEXT__ = "background";
|
||||
|
||||
const CSP_RULE_START_ID = 1;
|
||||
const ORI_RULE_START_ID = 10000;
|
||||
@@ -193,19 +191,21 @@ async function registerMsgDisplayScript() {
|
||||
/**
|
||||
* 插件安装
|
||||
*/
|
||||
browser.runtime.onInstalled.addListener(() => {
|
||||
tryInitDefaultData();
|
||||
browser.runtime.onInstalled.addListener(async () => {
|
||||
await tryInitDefaultData();
|
||||
|
||||
//在thunderbird中注册脚本
|
||||
if (process.env.REACT_APP_CLIENT === CLIENT_THUNDERBIRD) {
|
||||
registerMsgDisplayScript();
|
||||
}
|
||||
|
||||
const { contextMenuType, csplist, orilist } = await getSettingWithDefault();
|
||||
|
||||
// 右键菜单
|
||||
addContextMenus();
|
||||
addContextMenus(contextMenuType);
|
||||
|
||||
// 禁用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_TRANS_TAG = "font";
|
||||
// export const DEFAULT_SELECT_STYLE =
|
||||
// "-webkit-line-clamp: unset; max-height: none; height: auto;";
|
||||
export const DEFAULT_SELECT_STYLE =
|
||||
"-webkit-line-clamp: unset; max-height: none; height: auto;";
|
||||
|
||||
export const OPT_TIMING_PAGESCROLL = "mk_pagescroll"; // 滚动加载翻译
|
||||
export const OPT_TIMING_PAGEOPEN = "mk_pageopen"; // 直接翻译到底
|
||||
@@ -108,7 +108,7 @@ export const GLOBLA_RULE = {
|
||||
textExtStyle: "", // 译文附加样式
|
||||
termsStyle: "font-weight: bold;", // 专业术语样式
|
||||
highlightStyle: "color: red;", // 高亮词汇样式
|
||||
selectStyle: "", // 选择器节点样式
|
||||
selectStyle: DEFAULT_SELECT_STYLE, // 选择器节点样式
|
||||
parentStyle: "", // 选择器父节点样式
|
||||
grandStyle: "", // 选择器祖节点样式
|
||||
injectJs: "", // 注入JS
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { run } from "./common";
|
||||
|
||||
globalThis.__KISS_CONTEXT__ = "content";
|
||||
|
||||
run();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { apiBaiduTTS } from "../apis";
|
||||
import { kissLog } from "../libs/log";
|
||||
import { logger } from "../libs/log";
|
||||
import { fetchData } from "../libs/fetch";
|
||||
|
||||
/**
|
||||
* 声音播放hook
|
||||
@@ -12,50 +12,97 @@ export function useAudio(src) {
|
||||
const [error, setError] = useState(null);
|
||||
const [ready, setReady] = useState(false);
|
||||
const [playing, setPlaying] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const onPlay = useCallback(() => {
|
||||
audioRef.current?.play();
|
||||
const onPlay = useCallback(async () => {
|
||||
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(() => {
|
||||
if (!src) {
|
||||
return;
|
||||
}
|
||||
const audio = new Audio(src);
|
||||
audio.addEventListener("error", (err) => setError(err));
|
||||
audio.addEventListener("canplaythrough", () => setReady(true));
|
||||
audio.addEventListener("play", () => setPlaying(true));
|
||||
audio.addEventListener("ended", () => setPlaying(false));
|
||||
if (!src) return;
|
||||
|
||||
let ignore = false;
|
||||
let objectUrl = null;
|
||||
|
||||
setReady(false);
|
||||
setError(null);
|
||||
setPlaying(false);
|
||||
setLoading(true);
|
||||
|
||||
const audio = new 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]);
|
||||
|
||||
return {
|
||||
loading,
|
||||
error,
|
||||
ready,
|
||||
playing,
|
||||
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: () => {},
|
||||
});
|
||||
|
||||
export function SettingProvider({ children, isSettingPage }) {
|
||||
export function SettingProvider({ children, context }) {
|
||||
const isOptionsPage = useMemo(() => context === "options", [context]);
|
||||
|
||||
const {
|
||||
data: setting,
|
||||
isLoading,
|
||||
update,
|
||||
reload,
|
||||
} = useStorage(
|
||||
STOKEY_SETTING,
|
||||
DEFAULT_SETTING,
|
||||
isSettingPage ? KV_SETTING_KEY : ""
|
||||
);
|
||||
} = useStorage(STOKEY_SETTING, DEFAULT_SETTING, KV_SETTING_KEY);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof setting?.darkMode === "boolean") {
|
||||
@@ -47,7 +45,7 @@ export function SettingProvider({ children, isSettingPage }) {
|
||||
}, [setting?.darkMode, update]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isSettingPage) return;
|
||||
if (!isOptionsPage) return;
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
@@ -59,7 +57,7 @@ export function SettingProvider({ children, isSettingPage }) {
|
||||
logger.error("Failed to fetch log level, using default.", error);
|
||||
}
|
||||
})();
|
||||
}, [isSettingPage, setting?.logLevel]);
|
||||
}, [isOptionsPage, setting?.logLevel]);
|
||||
|
||||
const updateSetting = useCallback(
|
||||
(objOrFn) => {
|
||||
@@ -81,28 +79,31 @@ export function SettingProvider({ children, isSettingPage }) {
|
||||
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
context,
|
||||
setting,
|
||||
updateSetting,
|
||||
updateChild,
|
||||
reloadSetting: reload,
|
||||
}),
|
||||
[setting, updateSetting, updateChild, reload]
|
||||
[context, setting, updateSetting, updateChild, reload]
|
||||
);
|
||||
|
||||
if (isLoading) {
|
||||
return <Loading />;
|
||||
return isOptionsPage ? <Loading /> : null;
|
||||
}
|
||||
|
||||
if (!setting) {
|
||||
<center>
|
||||
<Alert severity="error" sx={{ maxWidth: 600, margin: "60px auto" }}>
|
||||
<p>数据加载出错,请刷新页面或卸载后重新安装。</p>
|
||||
<p>
|
||||
Data loading error, please refresh the page or uninstall and
|
||||
reinstall.
|
||||
</p>
|
||||
</Alert>
|
||||
</center>;
|
||||
return isOptionsPage ? (
|
||||
<center>
|
||||
<Alert severity="error" sx={{ maxWidth: 600, margin: "60px auto" }}>
|
||||
<p>数据加载出错,请刷新页面或卸载后重新安装。</p>
|
||||
<p>
|
||||
Data loading error, please refresh the page or uninstall and
|
||||
reinstall.
|
||||
</p>
|
||||
</Alert>
|
||||
</center>
|
||||
) : null;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -3,6 +3,7 @@ import { storage } from "../libs/storage";
|
||||
import { kissLog } from "../libs/log";
|
||||
import { syncData } from "../libs/sync";
|
||||
import { useDebouncedCallback } from "./DebouncedCallback";
|
||||
import { isOptions } from "../libs/browser";
|
||||
|
||||
/**
|
||||
* 用于将组件状态与 Storage 同步
|
||||
@@ -79,7 +80,7 @@ export function useStorage(key, defaultVal = null, syncKey = "") {
|
||||
});
|
||||
|
||||
// 触发远端同步
|
||||
if (syncKey) {
|
||||
if (syncKey && isOptions()) {
|
||||
debouncedSync(syncKey, data);
|
||||
}
|
||||
}, [key, syncKey, isLoading, data, debouncedSync]);
|
||||
|
||||
@@ -14,7 +14,30 @@ function _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 =
|
||||
"LanguageDetector" in globalThis && "Translator" in globalThis;
|
||||
|
||||
@@ -16,7 +16,7 @@ import { blobToBase64 } from "./utils";
|
||||
*/
|
||||
export const tryClearCaches = async () => {
|
||||
try {
|
||||
if (isExt && !isBg) {
|
||||
if (isExt && !isBg()) {
|
||||
await sendBgMsg(MSG_CLEAR_CACHES);
|
||||
} else {
|
||||
await caches.delete(CACHE_NAME);
|
||||
@@ -50,13 +50,13 @@ const newCacheReq = async (input, init) => {
|
||||
* @param {*} init
|
||||
* @returns
|
||||
*/
|
||||
export const getHttpCache = async ({ input, init }) => {
|
||||
export const getHttpCache = async ({ input, init, expect }) => {
|
||||
try {
|
||||
const request = await newCacheReq(input, init);
|
||||
const cache = await caches.open(CACHE_NAME);
|
||||
const response = await cache.match(request);
|
||||
if (response) {
|
||||
const res = await parseResponse(response);
|
||||
const res = await parseResponse(response, expect);
|
||||
return res;
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -99,7 +99,7 @@ export const putHttpCache = async ({
|
||||
* @param {*} res
|
||||
* @returns
|
||||
*/
|
||||
export const parseResponse = async (res) => {
|
||||
export const parseResponse = async (res, expect = null) => {
|
||||
if (!res) {
|
||||
throw new Error("Response object does not exist");
|
||||
}
|
||||
@@ -108,21 +108,45 @@ export const parseResponse = async (res) => {
|
||||
const msg = {
|
||||
url: res.url,
|
||||
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));
|
||||
}
|
||||
|
||||
const contentType = res.headers.get("Content-Type");
|
||||
if (contentType?.includes("json")) {
|
||||
return res.json();
|
||||
} else if (contentType?.includes("audio")) {
|
||||
const contentType = res.headers.get("Content-Type") || "";
|
||||
if (expect === "blob") return res.blob();
|
||||
if (expect === "text") return res.text();
|
||||
if (expect === "json") return res.json();
|
||||
if (
|
||||
expect === "audio" ||
|
||||
contentType.includes("audio") ||
|
||||
contentType.includes("image") ||
|
||||
contentType.includes("video")
|
||||
) {
|
||||
const blob = await res.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 }) => {
|
||||
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 () => {};
|
||||
|
||||
const targetKeySet = new Set(targetKeys);
|
||||
let hasInterference = false;
|
||||
const onKeyDown = (pressedKeys, event) => {
|
||||
if (isSameSet(targetKeySet, pressedKeys)) {
|
||||
// event.preventDefault(); // 阻止浏览器的默认行为
|
||||
// event.stopPropagation(); // 阻止事件继续(向父元素)冒泡
|
||||
fn();
|
||||
// if (isSameSet(targetKeySet, pressedKeys)) {
|
||||
// // event.preventDefault(); // 阻止浏览器的默认行为
|
||||
// // event.stopPropagation(); // 阻止事件继续(向父元素)冒泡
|
||||
// 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);
|
||||
};
|
||||
|
||||
@@ -2,6 +2,8 @@ import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import Options from "./views/Options";
|
||||
|
||||
globalThis.__KISS_CONTEXT__ = "options";
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById("root"));
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
|
||||
@@ -4,10 +4,12 @@ import { SettingProvider } from "./hooks/Setting";
|
||||
import ThemeProvider from "./hooks/Theme";
|
||||
import Popup from "./views/Popup";
|
||||
|
||||
globalThis.__KISS_CONTEXT__ = "popup";
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById("root"));
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<SettingProvider>
|
||||
<SettingProvider context="popup">
|
||||
<ThemeProvider>
|
||||
<Popup />
|
||||
</ThemeProvider>
|
||||
|
||||
@@ -45,7 +45,7 @@ export default function ContentFab({
|
||||
);
|
||||
|
||||
return (
|
||||
<SettingProvider>
|
||||
<SettingProvider context="fab">
|
||||
<ThemeProvider>
|
||||
<Draggable
|
||||
key="fab"
|
||||
|
||||
@@ -70,7 +70,7 @@ export default function Action({ translator, processActions }) {
|
||||
}, [windowSize]);
|
||||
|
||||
return (
|
||||
<SettingProvider>
|
||||
<SettingProvider context="contentPopup">
|
||||
<ThemeProvider>
|
||||
{showPopup && (
|
||||
<Draggable
|
||||
|
||||
@@ -99,7 +99,7 @@ export default function Options() {
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingProvider isSettingPage={true}>
|
||||
<SettingProvider context="options">
|
||||
<ThemeProvider>
|
||||
<AlertProvider>
|
||||
<ConfirmProvider>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import VolumeUpIcon from "@mui/icons-material/VolumeUp";
|
||||
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);
|
||||
|
||||
if (error || !ready) {
|
||||
@@ -27,3 +28,10 @@ export default function AudioBtn({ src }) {
|
||||
</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 AudioBtn from "./AudioBtn";
|
||||
import { AudioBtn, BaiduAudioBtn } from "./AudioBtn";
|
||||
import { OPT_DICT_BING, OPT_DICT_YOUDAO } from "../../config";
|
||||
import { apiMicrosoftDict, apiYoudaoDict } from "../../apis";
|
||||
|
||||
@@ -48,12 +48,14 @@ export const dictHandlers = {
|
||||
style={{ display: "inline-block", paddingRight: "1em" }}
|
||||
>
|
||||
<Typography component="span">{`UK [${data?.ec?.word?.ukphone}]`}</Typography>
|
||||
<BaiduAudioBtn text={data?.ec?.word?.["return-phrase"]} lan="uk" />
|
||||
</Typography>
|
||||
<Typography
|
||||
component="div"
|
||||
style={{ display: "inline-block", paddingRight: "1em" }}
|
||||
>
|
||||
<Typography component="span">{`US [${data?.ec?.word?.usphone}]`}</Typography>
|
||||
<BaiduAudioBtn text={data?.ec?.word?.["return-phrase"]} lan="en" />
|
||||
</Typography>
|
||||
</Typography>
|
||||
),
|
||||
|
||||
@@ -141,7 +141,7 @@ export default function TranBox({
|
||||
const [mouseHover, setMouseHover] = useState(false);
|
||||
// todo: 这里的 SettingProvider 不应和 background 的共用
|
||||
return (
|
||||
<SettingProvider>
|
||||
<SettingProvider context="tranbox">
|
||||
<ThemeProvider styles={extStyles}>
|
||||
{showBox && (
|
||||
<DraggableResizable
|
||||
|
||||
@@ -133,7 +133,7 @@ export default function Slection({
|
||||
|
||||
useEffect(() => {
|
||||
async function handleMouseup(e) {
|
||||
e.stopPropagation();
|
||||
// e.stopPropagation();
|
||||
await sleep(200);
|
||||
|
||||
const selection = window.getSelection();
|
||||
|
||||
Reference in New Issue
Block a user