Compare commits

...

24 Commits

Author SHA1 Message Date
Gabe Yuan
e0b7c60099 v1.6.4 2023-09-02 20:07:24 +08:00
Gabe Yuan
536b58bf67 fix fuzzy style hover bug 2023-09-02 19:55:26 +08:00
Gabe Yuan
6bb742f828 v1.6.3 2023-09-02 19:16:14 +08:00
Gabe Yuan
72742e5e12 fix build:script 2023-09-02 19:09:12 +08:00
Gabe Yuan
3667e0a509 update readme 2023-09-02 18:14:42 +08:00
Gabe Yuan
c2d7668ba7 v1.6.2 2023-09-02 17:26:37 +08:00
Gabe Yuan
aa830f5e20 update readme 2023-09-02 17:22:35 +08:00
Gabe Yuan
b593fa4146 add deepl api 2023-09-02 16:57:09 +08:00
Gabe Yuan
b00b906484 optimize inject script 2023-09-02 15:54:51 +08:00
Gabe Yuan
c1bd6a1be6 use random eventname 2023-09-02 14:14:27 +08:00
Gabe Yuan
36739f04b3 add more shortcut 2023-09-02 13:14:27 +08:00
Gabe Yuan
23eb92853e register a shortcut for userscript 2023-09-02 00:42:15 +08:00
Gabe Yuan
5ab2910dc7 v1.6.1 2023-09-01 22:46:41 +08:00
Gabe Yuan
40d07f6764 fix proxy link 2023-09-01 22:28:28 +08:00
Gabe Yuan
5c8e216169 overwrite subscribe rules 2023-09-01 22:27:25 +08:00
Gabe Yuan
5ba061deda merge yarn 2023-09-01 19:35:48 +08:00
Gabe Yuan
935c83185d fix style text 2023-09-01 17:07:21 +08:00
Gabe Yuan
6327391e65 hide color input when diy style 2023-09-01 17:02:47 +08:00
Gabe Yuan
3d656cf5b0 use debounce to sync setting 2023-09-01 16:39:57 +08:00
Gabe Yuan
d570a0f1a2 replace 'document.documentElement.clientWidth' to 'window.innerWidth' 2023-09-01 16:11:31 +08:00
Gabe Yuan
503a71302c support diy text styles 2023-09-01 15:57:05 +08:00
Gabe Yuan
3e36ceb5b9 num of newline characters 2023-09-01 11:21:06 +08:00
Gabe Yuan
cde7a1d49f add kiss-proxy link 2023-09-01 11:03:53 +08:00
Gabe Yuan
b14a38e4fb sync then reload setting 2023-09-01 10:15:57 +08:00
29 changed files with 1359 additions and 577 deletions

2
.env
View File

@@ -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=1.6.0 REACT_APP_VERSION=1.6.4
REACT_APP_HOMEPAGE=https://github.com/fishjar/kiss-translator REACT_APP_HOMEPAGE=https://github.com/fishjar/kiss-translator

View File

@@ -1,10 +1,10 @@
## KISS Translator # KISS Translator
A minimalist [bilingual translation Extension & Greasemonkey Script](https://github.com/fishjar/kiss-translator). A minimalist [bilingual translation Extension & Greasemonkey Script](https://github.com/fishjar/kiss-translator).
[kiss-translator.webm](https://github.com/fishjar/kiss-translator/assets/1157624/f7ba8a5c-e4a8-4d5a-823a-5c5c67a0a47f) [kiss-translator.webm](https://github.com/fishjar/kiss-translator/assets/1157624/f7ba8a5c-e4a8-4d5a-823a-5c5c67a0a47f)
### Inspiration ## Inspiration
The inspiration for this project comes from [Immersive Translate](https://github.com/immersive-translate/immersive-translate). After trying it out, I found that it can be used together with the [Webpage Word Translation Extension](https://github.com/fishjar/kiss-dictionary) developed by me earlier, which just forms a very good supplement. The inspiration for this project comes from [Immersive Translate](https://github.com/immersive-translate/immersive-translate). After trying it out, I found that it can be used together with the [Webpage Word Translation Extension](https://github.com/fishjar/kiss-dictionary) developed by me earlier, which just forms a very good supplement.
@@ -14,11 +14,11 @@ It just so happens that I am obsessed with translation tools. Based on the conce
If you also like a little more simplicity, welcome to pick it up. If you also like a little more simplicity, welcome to pick it up.
### Features ## Features
- Keep it simple, smart - Keep it simple, smart
### Schedule ## Schedule
- [x] Provide trial installation package - [x] Provide trial installation package
- [x] Adapt browser - [x] Adapt browser
@@ -31,7 +31,7 @@ If you also like a little more simplicity, welcome to pick it up.
- [x] Google - [x] Google
- [x] Microsoft - [x] Microsoft
- [x] OpenAI - [x] OpenAI
- [ ] DeepL - [x] DeepL
- [x] Upload to app Store - [x] Upload to app Store
- [x] Chrome [Install Link](https://chrome.google.com/webstore/detail/kiss-translator/bdiifdefkgmcblbcghdlonllpjhhjgof) - [x] Chrome [Install Link](https://chrome.google.com/webstore/detail/kiss-translator/bdiifdefkgmcblbcghdlonllpjhhjgof)
- [x] Edge [Install Link](https://microsoftedge.microsoft.com/addons/detail/kiss-translator/jemckldkclkinpjighnoilpbldbdmmlh) - [x] Edge [Install Link](https://microsoftedge.microsoft.com/addons/detail/kiss-translator/jemckldkclkinpjighnoilpbldbdmmlh)
@@ -42,9 +42,10 @@ If you also like a little more simplicity, welcome to pick it up.
- [x] Data Synchronization Function - [x] Data Synchronization Function
- [x] Greasemonkey Script ([Setting Page 1](https://fishjar.github.io/kiss-translator/options.html)、[Setting Page 2](https://kiss-translator.rayjar.com/options)) - [x] Greasemonkey Script ([Setting Page 1](https://fishjar.github.io/kiss-translator/options.html)、[Setting Page 2](https://kiss-translator.rayjar.com/options))
- [x] [Tampermonkey](https://www.tampermonkey.net/) (Chrome/Edge/Firefox) [Install Link 1](https://fishjar.github.io/kiss-translator/kiss-translator.user.js)、[Install Link 2](https://kiss-translator.rayjar.com/kiss-translator.user.js) - [x] [Tampermonkey](https://www.tampermonkey.net/) (Chrome/Edge/Firefox) [Install Link 1](https://fishjar.github.io/kiss-translator/kiss-translator.user.js)、[Install Link 2](https://kiss-translator.rayjar.com/kiss-translator.user.js)
- [x] [Violentmonkey](https://violentmonkey.github.io/) (Chrome/Edge/Firefox) [Install Link 1](https://fishjar.github.io/kiss-translator/kiss-translator.user.js)、[Install Link 2](https://kiss-translator.rayjar.com/kiss-translator.user.js)
- [x] [Userscripts Safari](https://github.com/quoid/userscripts) (iOS Safari) [Install Link 1](https://fishjar.github.io/kiss-translator/kiss-translator-ios-safari.user.js)、[Install Link 2](https://kiss-translator.rayjar.com/kiss-translator.user-ios-safari.js) - [x] [Userscripts Safari](https://github.com/quoid/userscripts) (iOS Safari) [Install Link 1](https://fishjar.github.io/kiss-translator/kiss-translator-ios-safari.user.js)、[Install Link 2](https://kiss-translator.rayjar.com/kiss-translator.user-ios-safari.js)
### Guide ## Guide
```sh ```sh
git clone https://github.com/fishjar/kiss-translator.git git clone https://github.com/fishjar/kiss-translator.git
@@ -53,10 +54,10 @@ yarn install
yarn build yarn build
``` ```
### Data Sync ## Data Sync
Goto: [https://github.com/fishjar/kiss-worker](https://github.com/fishjar/kiss-worker) Goto: [https://github.com/fishjar/kiss-worker](https://github.com/fishjar/kiss-worker)
### Discussion ## Discussion
- Join [Telegram Group](https://t.me/+RRCu_4oNwrM2NmFl) - Join [Telegram Group](https://t.me/+RRCu_4oNwrM2NmFl)

View File

@@ -1,10 +1,10 @@
## 简约翻译 # 简约翻译
一个简约的 [双语网页翻译扩展 & 油猴脚本](https://github.com/fishjar/kiss-translator)。 一个简约的 [双语网页翻译扩展 & 油猴脚本](https://github.com/fishjar/kiss-translator)。
[kiss-translator.webm](https://github.com/fishjar/kiss-translator/assets/1157624/f7ba8a5c-e4a8-4d5a-823a-5c5c67a0a47f) [kiss-translator.webm](https://github.com/fishjar/kiss-translator/assets/1157624/f7ba8a5c-e4a8-4d5a-823a-5c5c67a0a47f)
### 缘由 ## 缘由
本项目灵感来源于 [Immersive Translate](https://github.com/immersive-translate/immersive-translate),在试用了后,发现搭配本人早前开发的 [网页划词翻译扩展](https://github.com/fishjar/kiss-dictionary) 一起使用,刚好形成很好补充。 本项目灵感来源于 [Immersive Translate](https://github.com/immersive-translate/immersive-translate),在试用了后,发现搭配本人早前开发的 [网页划词翻译扩展](https://github.com/fishjar/kiss-dictionary) 一起使用,刚好形成很好补充。
@@ -14,11 +14,11 @@
如果你也喜欢简约一点的,欢迎自取。 如果你也喜欢简约一点的,欢迎自取。
### 特点 ## 特点
- 保持简约 - 保持简约
### 进度 ## 进度
- [x] 提供试用安装包 - [x] 提供试用安装包
- [x] 适配浏览器 - [x] 适配浏览器
@@ -31,7 +31,7 @@
- [x] Google - [x] Google
- [x] Microsoft - [x] Microsoft
- [x] OpenAI - [x] OpenAI
- [ ] DeepL - [x] DeepL
- [x] 上架应用市场 - [x] 上架应用市场
- [x] Chrome [安装地址](https://chrome.google.com/webstore/detail/kiss-translator/bdiifdefkgmcblbcghdlonllpjhhjgof?hl=zh-CN) - [x] Chrome [安装地址](https://chrome.google.com/webstore/detail/kiss-translator/bdiifdefkgmcblbcghdlonllpjhhjgof?hl=zh-CN)
- [x] Edge [安装地址](https://microsoftedge.microsoft.com/addons/detail/%E7%AE%80%E7%BA%A6%E7%BF%BB%E8%AF%91/jemckldkclkinpjighnoilpbldbdmmlh?hl=zh-CN) - [x] Edge [安装地址](https://microsoftedge.microsoft.com/addons/detail/%E7%AE%80%E7%BA%A6%E7%BF%BB%E8%AF%91/jemckldkclkinpjighnoilpbldbdmmlh?hl=zh-CN)
@@ -42,9 +42,10 @@
- [x] 数据同步功能 - [x] 数据同步功能
- [x] 油猴脚本 ([设置页面 1](https://fishjar.github.io/kiss-translator/options.html)、[设置页面 2](https://kiss-translator.rayjar.com/options)) - [x] 油猴脚本 ([设置页面 1](https://fishjar.github.io/kiss-translator/options.html)、[设置页面 2](https://kiss-translator.rayjar.com/options))
- [x] [Tampermonkey](https://www.tampermonkey.net/) (Chrome/Edge/Firefox) [安装链接 1](https://fishjar.github.io/kiss-translator/kiss-translator.user.js)、[安装链接 2](https://kiss-translator.rayjar.com/kiss-translator.user.js) - [x] [Tampermonkey](https://www.tampermonkey.net/) (Chrome/Edge/Firefox) [安装链接 1](https://fishjar.github.io/kiss-translator/kiss-translator.user.js)、[安装链接 2](https://kiss-translator.rayjar.com/kiss-translator.user.js)
- [x] [Violentmonkey](https://violentmonkey.github.io/) (Chrome/Edge/Firefox) [安装链接 1](https://fishjar.github.io/kiss-translator/kiss-translator.user.js)、[安装链接 2](https://kiss-translator.rayjar.com/kiss-translator.user.js)
- [x] [Userscripts Safari](https://github.com/quoid/userscripts) (iOS Safari) [安装链接 1](https://fishjar.github.io/kiss-translator/kiss-translator-ios-safari.user.js)、[安装链接 2](https://kiss-translator.rayjar.com/kiss-translator.user-ios-safari.js) - [x] [Userscripts Safari](https://github.com/quoid/userscripts) (iOS Safari) [安装链接 1](https://fishjar.github.io/kiss-translator/kiss-translator-ios-safari.user.js)、[安装链接 2](https://kiss-translator.rayjar.com/kiss-translator.user-ios-safari.js)
### 指引 ## 指引
```sh ```sh
git clone https://github.com/fishjar/kiss-translator.git git clone https://github.com/fishjar/kiss-translator.git
@@ -53,10 +54,10 @@ yarn install
yarn build yarn build
``` ```
### 数据同步 ## 数据同步
移步: [https://github.com/fishjar/kiss-worker](https://github.com/fishjar/kiss-worker) 移步: [https://github.com/fishjar/kiss-worker](https://github.com/fishjar/kiss-worker)
### 交流 ## 交流
- 加入 [Telegram 群](https://t.me/+RRCu_4oNwrM2NmFl) - 加入 [Telegram 群](https://t.me/+RRCu_4oNwrM2NmFl)

View File

@@ -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": "1.6.0", "version": "1.6.4",
"author": "Gabe<yugang2002@gmail.com>", "author": "Gabe<yugang2002@gmail.com>",
"private": true, "private": true,
"dependencies": { "dependencies": {
@@ -9,12 +9,14 @@
"@emotion/styled": "^11.10.8", "@emotion/styled": "^11.10.8",
"@mui/icons-material": "^5.11.11", "@mui/icons-material": "^5.11.11",
"@mui/material": "^5.11.12", "@mui/material": "^5.11.12",
"@violentmonkey/shortcut": "^1.3.0",
"query-string": "^8.1.0", "query-string": "^8.1.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-markdown": "^8.0.7", "react-markdown": "^8.0.7",
"react-router-dom": "^6.10.0", "react-router-dom": "^6.10.0",
"react-scripts": "5.0.1", "react-scripts": "5.0.1",
"styled-components": "^6.0.7",
"webextension-polyfill": "^0.10.0" "webextension-polyfill": "^0.10.0"
}, },
"scripts": { "scripts": {
@@ -24,10 +26,10 @@
"build:edge": "rm -rf build/edge && cp -r build/chrome build/edge", "build:edge": "rm -rf build/edge && cp -r build/chrome build/edge",
"build:firefox": "rm -rf build/firefox && cp -r build/chrome build/firefox && cat ./build/firefox/manifest.firefox.json > ./build/firefox/manifest.json", "build:firefox": "rm -rf build/firefox && cp -r build/chrome build/firefox && cat ./build/firefox/manifest.firefox.json > ./build/firefox/manifest.json",
"build:web": "rm -rf build/web && BUILD_PATH=./build/web REACT_APP_CLIENT=userscript react-app-rewired build", "build:web": "rm -rf build/web && BUILD_PATH=./build/web REACT_APP_CLIENT=userscript react-app-rewired build",
"build:userscript": "rm -rf build/userscript && mkdir build/userscript && cp build/web/kiss-translator.user.js build/userscript/kiss-translator.user.js", "build:userscript-ios": "file1=build/web/kiss-translator.user.js file2=build/web/kiss-translator-ios-safari.user.js && cp $file1 $file2 && sed -i 's|// @grant unsafeWindow|// @inject-into content|g' $file2",
"build:userscript-ios": "file1=build/userscript/kiss-translator.user.js file2=build/userscript/kiss-translator-ios-safari.user.js && cp $file1 $file2 && sed -i 's|// @grant unsafeWindow|// @inject-into content|g' $file2", "build:userscript": "rm -rf build/userscript && mkdir build/userscript && cp build/web/*.user.js build/userscript/",
"build:rules": "babel-node src/rules.js", "build:rules": "babel-node src/rules.js",
"build": "yarn build:chrome && yarn build:edge && yarn build:firefox && yarn build:web && yarn build:userscript && yarn build:userscript-ios && yarn build:rules", "build": "yarn build:chrome && yarn build:edge && yarn build:firefox && yarn build:web && yarn build:userscript-ios && yarn build:userscript && yarn build:rules",
"deploy:web": "wrangler pages deploy ./build/web --project-name kiss-translator", "deploy:web": "wrangler pages deploy ./build/web --project-name kiss-translator",
"test": "react-app-rewired test", "test": "react-app-rewired test",
"eject": "react-scripts eject" "eject": "react-scripts eject"

View File

@@ -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": "1.6.0", "version": "1.6.4",
"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",
@@ -17,6 +17,11 @@
} }
], ],
"commands": { "commands": {
"_execute_browser_action": {
"suggested_key": {
"default": "Alt+K"
}
},
"toggleTranslate": { "toggleTranslate": {
"suggested_key": { "suggested_key": {
"default": "Alt+Q" "default": "Alt+Q"

View File

@@ -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": "1.6.0", "version": "1.6.4",
"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",
@@ -18,6 +18,11 @@
} }
], ],
"commands": { "commands": {
"_execute_action": {
"suggested_key": {
"default": "Alt+K"
}
},
"toggleTranslate": { "toggleTranslate": {
"suggested_key": { "suggested_key": {
"default": "Alt+Q" "default": "Alt+Q"

View File

@@ -3,6 +3,7 @@ import { fetchPolyfill } from "../libs/fetch";
import { import {
OPT_TRANS_GOOGLE, OPT_TRANS_GOOGLE,
OPT_TRANS_MICROSOFT, OPT_TRANS_MICROSOFT,
OPT_TRANS_DEEPL,
OPT_TRANS_OPENAI, OPT_TRANS_OPENAI,
URL_MICROSOFT_TRANS, URL_MICROSOFT_TRANS,
OPT_LANGS_SPECIAL, OPT_LANGS_SPECIAL,
@@ -95,6 +96,36 @@ const apiMicrosoftTranslate = (translator, text, to, from) => {
}); });
}; };
/**
* DeepL翻译
* @param {*} text
* @param {*} to
* @param {*} from
* @returns
*/
const apiDeepLTranslate = (translator, text, to, from, setting) => {
const { deeplUrl, deeplKey } = setting;
const data = {
text: [text],
target_lang: to,
split_sentences: "0",
};
if (from) {
data.source_lang = from;
}
return fetchPolyfill(deeplUrl, {
headers: {
"Content-type": "application/json",
},
method: "POST",
body: JSON.stringify(data),
useCache: true,
usePool: true,
translator,
token: deeplKey,
});
};
/** /**
* OpenAI 翻译 * OpenAI 翻译
* @param {*} text * @param {*} text
@@ -160,6 +191,10 @@ export const apiTranslate = async ({
const res = await apiMicrosoftTranslate(translator, q, to, from); const res = await apiMicrosoftTranslate(translator, q, to, from);
trText = res[0].translations[0].text; trText = res[0].translations[0].text;
isSame = to === res[0].detectedLanguage.language; isSame = to === res[0].detectedLanguage.language;
} else if (translator === OPT_TRANS_DEEPL) {
const res = await apiDeepLTranslate(translator, q, to, from, setting);
trText = res.translations.map((item) => item.text).join(" ");
isSame = to === res.translations[0].detected_source_language;
} else if (translator === OPT_TRANS_OPENAI) { } else if (translator === OPT_TRANS_OPENAI) {
const res = await apiOpenaiTranslate(translator, q, to, from, setting); const res = await apiOpenaiTranslate(translator, q, to, from, setting);
trText = res?.choices?.[0].message.content; trText = res?.choices?.[0].message.content;

View File

@@ -60,6 +60,10 @@ export const I18N = {
zh: `最大翻译长度 (100-10000)`, zh: `最大翻译长度 (100-10000)`,
en: `Max Translate Length (100-10000)`, en: `Max Translate Length (100-10000)`,
}, },
num_of_newline_characters: {
zh: `换行字符数 (1-1000)`,
en: `Number of Newline Characters (1-1000)`,
},
translate_service: { translate_service: {
zh: `翻译服务`, zh: `翻译服务`,
en: `Translate Service`, en: `Translate Service`,
@@ -84,6 +88,10 @@ export const I18N = {
zh: `样式颜色`, zh: `样式颜色`,
en: `Style Color`, en: `Style Color`,
}, },
remain_unchanged: {
zh: `保留不变`,
en: `Remain Unchanged`,
},
google_api: { google_api: {
zh: `谷歌翻译接口`, zh: `谷歌翻译接口`,
en: `Google Translate API`, en: `Google Translate API`,
@@ -132,6 +140,10 @@ export const I18N = {
zh: `订阅规则`, zh: `订阅规则`,
en: `Subscribe Rules`, en: `Subscribe Rules`,
}, },
overwrite_subscribe_rules: {
zh: `覆写订阅规则`,
en: `Overwrite Subscribe Rules`,
},
subscribe_url: { subscribe_url: {
zh: `订阅地址`, zh: `订阅地址`,
en: `Subscribe URL`, en: `Subscribe URL`,
@@ -152,6 +164,10 @@ export const I18N = {
zh: `查看关于数据同步接口部署`, zh: `查看关于数据同步接口部署`,
en: `View About Data Synchronization Interface Deployment`, en: `View About Data Synchronization Interface Deployment`,
}, },
about_api_proxy: {
zh: `查看自建一个翻译接口代理`,
en: `Check out the self-built translation interface proxy`,
},
style_none: { style_none: {
zh: ``, zh: ``,
en: `None`, en: `None`,
@@ -180,6 +196,14 @@ export const I18N = {
zh: `高亮`, zh: `高亮`,
en: `Highlight`, en: `Highlight`,
}, },
diy_style: {
zh: `自定义样式`,
en: `Custom Style`,
},
diy_style_helper: {
zh: `遵循“styled-components”的语法`,
en: `Follow the syntax of "styled-components"`,
},
setting: { setting: {
zh: `设置`, zh: `设置`,
en: `Setting`, en: `Setting`,
@@ -193,8 +217,8 @@ export const I18N = {
en: `1. The asterisk (*) wildcard is supported. 2. Multiple URLs can be separated by English commas ",".`, en: `1. The asterisk (*) wildcard is supported. 2. Multiple URLs can be separated by English commas ",".`,
}, },
selector_helper: { selector_helper: {
zh: `1、遵循CSS选择器规则。2、留空表示采用全局设置。3、多个CSS选择器之间用“;”隔开。4、“shadow root”选择器和内部选择器用“>>>”隔开。`, zh: `1、遵循CSS选择器语法。2、留空表示采用全局设置。3、多个CSS选择器之间用“;”隔开。4、“shadow root”选择器和内部选择器用“>>>”隔开。`,
en: `1. Follow the CSS selector rules. 2. Leave blank to adopt the global setting. 3. Separate multiple CSS selectors with ";". 4. The "shadow root" selector and the internal selector are separated by ">>>".`, en: `1. Follow CSS selector syntax. 2. Leave blank to adopt the global setting. 3. Separate multiple CSS selectors with ";". 4. The "shadow root" selector and the internal selector are separated by ">>>".`,
}, },
translate_switch: { translate_switch: {
zh: `开启翻译`, zh: `开启翻译`,
@@ -236,6 +260,14 @@ export const I18N = {
zh: `请检查url地址是否正确或稍后再试。`, zh: `请检查url地址是否正确或稍后再试。`,
en: `Please check if the url address is correct or try again later.`, en: `Please check if the url address is correct or try again later.`,
}, },
deepl_api: {
zh: `DeepL 接口`,
en: `DeepL API`,
},
deepl_key: {
zh: `DeepL 密钥`,
en: `DeepL Key`,
},
openai_api: { openai_api: {
zh: `OpenAI 接口`, zh: `OpenAI 接口`,
en: `OpenAI API`, en: `OpenAI API`,

View File

@@ -1,13 +1,23 @@
import { import {
DEFAULT_SELECTOR, DEFAULT_SELECTOR,
GLOBAL_KEY, GLOBAL_KEY,
REMAIN_KEY,
SHADOW_KEY, SHADOW_KEY,
DEFAULT_RULE, DEFAULT_RULE,
DEFAULT_OW_RULE,
BUILTIN_RULES, BUILTIN_RULES,
} from "./rules"; } from "./rules";
import { APP_NAME, APP_LCNAME } from "./app"; import { APP_NAME, APP_LCNAME } from "./app";
export { I18N, UI_LANGS } from "./i18n"; export { I18N, UI_LANGS } from "./i18n";
export { GLOBAL_KEY, SHADOW_KEY, DEFAULT_RULE, BUILTIN_RULES, APP_LCNAME }; export {
GLOBAL_KEY,
REMAIN_KEY,
SHADOW_KEY,
DEFAULT_RULE,
DEFAULT_OW_RULE,
BUILTIN_RULES,
APP_LCNAME,
};
export const STOKEY_MSAUTH = `${APP_NAME}_msauth`; export const STOKEY_MSAUTH = `${APP_NAME}_msauth`;
export const STOKEY_SETTING = `${APP_NAME}_setting`; export const STOKEY_SETTING = `${APP_NAME}_setting`;
@@ -43,12 +53,11 @@ export const MSG_TRANS_GETRULE = "trans_getrule";
export const MSG_TRANS_PUTRULE = "trans_putrule"; export const MSG_TRANS_PUTRULE = "trans_putrule";
export const MSG_TRANS_CURRULE = "trans_currule"; export const MSG_TRANS_CURRULE = "trans_currule";
export const EVENT_KISS = "kissEvent";
export const THEME_LIGHT = "light"; export const THEME_LIGHT = "light";
export const THEME_DARK = "dark"; export const THEME_DARK = "dark";
export const URL_KISS_WORKER = "https://github.com/fishjar/kiss-worker"; export const URL_KISS_WORKER = "https://github.com/fishjar/kiss-worker";
export const URL_KISS_PROXY = "https://github.com/fishjar/kiss-proxy";
export const URL_RAW_PREFIX = export const URL_RAW_PREFIX =
"https://raw.githubusercontent.com/fishjar/kiss-translator/master"; "https://raw.githubusercontent.com/fishjar/kiss-translator/master";
export const URL_MICROSOFT_AUTH = "https://edge.microsoft.com/translate/auth"; export const URL_MICROSOFT_AUTH = "https://edge.microsoft.com/translate/auth";
@@ -57,10 +66,12 @@ export const URL_MICROSOFT_TRANS =
export const OPT_TRANS_GOOGLE = "Google"; export const OPT_TRANS_GOOGLE = "Google";
export const OPT_TRANS_MICROSOFT = "Microsoft"; export const OPT_TRANS_MICROSOFT = "Microsoft";
export const OPT_TRANS_DEEPL = "DeepL";
export const OPT_TRANS_OPENAI = "OpenAI"; export const OPT_TRANS_OPENAI = "OpenAI";
export const OPT_TRANS_ALL = [ export const OPT_TRANS_ALL = [
OPT_TRANS_GOOGLE, OPT_TRANS_GOOGLE,
OPT_TRANS_MICROSOFT, OPT_TRANS_MICROSOFT,
OPT_TRANS_DEEPL,
OPT_TRANS_OPENAI, OPT_TRANS_OPENAI,
]; ];
@@ -110,6 +121,12 @@ export const OPT_LANGS_SPECIAL = {
["zh-CN", "zh-Hans"], ["zh-CN", "zh-Hans"],
["zh-TW", "zh-Hant"], ["zh-TW", "zh-Hant"],
]), ]),
[OPT_TRANS_DEEPL]: new Map([
...OPT_LANGS_FROM.map(([key]) => [key, key.toUpperCase()]),
["auto", ""],
["zh-CN", "ZH"],
["zh-TW", "ZH"],
]),
[OPT_TRANS_OPENAI]: new Map( [OPT_TRANS_OPENAI]: new Map(
OPT_LANGS_FROM.map(([key, val]) => [key, val.split(" - ")[0]]) OPT_LANGS_FROM.map(([key, val]) => [key, val.split(" - ")[0]])
), ),
@@ -121,7 +138,8 @@ export const OPT_STYLE_DOTLINE = "dot_line"; // 点状线
export const OPT_STYLE_DASHLINE = "dash_line"; // 虚线 export const OPT_STYLE_DASHLINE = "dash_line"; // 虚线
export const OPT_STYLE_WAVYLINE = "wavy_line"; // 波浪线 export const OPT_STYLE_WAVYLINE = "wavy_line"; // 波浪线
export const OPT_STYLE_FUZZY = "fuzzy"; // 模糊 export const OPT_STYLE_FUZZY = "fuzzy"; // 模糊
export const OPT_STYLE_HIGHTLIGHT = "highlight"; // 高亮 export const OPT_STYLE_HIGHLIGHT = "highlight"; // 高亮
export const OPT_STYLE_DIY = "diy_style"; // 自定义样式
export const OPT_STYLE_ALL = [ export const OPT_STYLE_ALL = [
OPT_STYLE_NONE, OPT_STYLE_NONE,
OPT_STYLE_LINE, OPT_STYLE_LINE,
@@ -129,7 +147,15 @@ export const OPT_STYLE_ALL = [
OPT_STYLE_DASHLINE, OPT_STYLE_DASHLINE,
OPT_STYLE_WAVYLINE, OPT_STYLE_WAVYLINE,
OPT_STYLE_FUZZY, OPT_STYLE_FUZZY,
OPT_STYLE_HIGHTLIGHT, OPT_STYLE_HIGHLIGHT,
OPT_STYLE_DIY,
];
export const OPT_STYLE_USE_COLOR = [
OPT_STYLE_LINE,
OPT_STYLE_DOTLINE,
OPT_STYLE_DASHLINE,
OPT_STYLE_WAVYLINE,
OPT_STYLE_HIGHLIGHT,
]; ];
export const DEFAULT_FETCH_LIMIT = 10; // 默认最大任务数量 export const DEFAULT_FETCH_LIMIT = 10; // 默认最大任务数量
@@ -150,6 +176,7 @@ export const GLOBLA_RULE = {
textStyle: OPT_STYLE_DASHLINE, textStyle: OPT_STYLE_DASHLINE,
transOpen: "false", transOpen: "false",
bgColor: "", bgColor: "",
textDiyStyle: "",
}; };
// 订阅列表 // 订阅列表
@@ -166,6 +193,7 @@ export const DEFAULT_SUBRULES_LIST = [
export const TRANS_MIN_LENGTH = 5; // 最短翻译长度 export const TRANS_MIN_LENGTH = 5; // 最短翻译长度
export const TRANS_MAX_LENGTH = 5000; // 最长翻译长度 export const TRANS_MAX_LENGTH = 5000; // 最长翻译长度
export const TRANS_NEWLINE_LENGTH = 40; // 换行字符数
export const DEFAULT_SETTING = { export const DEFAULT_SETTING = {
darkMode: false, // 深色模式 darkMode: false, // 深色模式
@@ -174,10 +202,14 @@ export const DEFAULT_SETTING = {
fetchInterval: DEFAULT_FETCH_INTERVAL, // 任务间隔时间 fetchInterval: DEFAULT_FETCH_INTERVAL, // 任务间隔时间
minLength: TRANS_MIN_LENGTH, minLength: TRANS_MIN_LENGTH,
maxLength: TRANS_MAX_LENGTH, maxLength: TRANS_MAX_LENGTH,
newlineLength: TRANS_NEWLINE_LENGTH,
clearCache: false, // 是否在浏览器下次启动时清除缓存 clearCache: false, // 是否在浏览器下次启动时清除缓存
injectRules: true, // 是否注入订阅规则 injectRules: true, // 是否注入订阅规则
subrulesList: DEFAULT_SUBRULES_LIST, // 订阅列表 subrulesList: DEFAULT_SUBRULES_LIST, // 订阅列表
owSubrule: DEFAULT_OW_RULE, // 覆写订阅规则
googleUrl: "https://translate.googleapis.com/translate_a/single", // 谷歌翻译接口 googleUrl: "https://translate.googleapis.com/translate_a/single", // 谷歌翻译接口
deeplUrl: "https://api-free.deepl.com/v2/translate",
deeplKey: "",
openaiUrl: "https://api.openai.com/v1/chat/completions", openaiUrl: "https://api.openai.com/v1/chat/completions",
openaiKey: "", openaiKey: "",
openaiModel: "gpt-4", openaiModel: "gpt-4",

View File

@@ -3,6 +3,7 @@ const els = `li, p, h1, h2, h3, h4, h5, h6, dd`;
export const DEFAULT_SELECTOR = `:is(${els})`; export const DEFAULT_SELECTOR = `:is(${els})`;
export const GLOBAL_KEY = "*"; export const GLOBAL_KEY = "*";
export const REMAIN_KEY = "-";
export const SHADOW_KEY = ">>>"; export const SHADOW_KEY = ">>>";
@@ -15,6 +16,30 @@ export const DEFAULT_RULE = {
textStyle: GLOBAL_KEY, textStyle: GLOBAL_KEY,
transOpen: GLOBAL_KEY, transOpen: GLOBAL_KEY,
bgColor: "", bgColor: "",
textDiyStyle: "",
};
const DEFAULT_DIY_STYLE = `color: #666;
background: linear-gradient(
45deg,
LightGreen 20%,
LightPink 20% 40%,
LightSalmon 40% 60%,
LightSeaGreen 60% 80%,
LightSkyBlue 80%
);
&:hover {
color: #333;
};`;
export const DEFAULT_OW_RULE = {
translator: REMAIN_KEY,
fromLang: REMAIN_KEY,
toLang: REMAIN_KEY,
textStyle: REMAIN_KEY,
transOpen: REMAIN_KEY,
bgColor: "",
textDiyStyle: DEFAULT_DIY_STYLE,
}; };
const RULES = [ const RULES = [

View File

@@ -2,28 +2,38 @@ import { STOKEY_SETTING, DEFAULT_SETTING } from "../config";
import { useStorage } from "./Storage"; import { useStorage } from "./Storage";
import { useSync } from "./Sync"; import { useSync } from "./Sync";
import { trySyncSetting } from "../libs/sync"; import { trySyncSetting } from "../libs/sync";
import { createContext, useCallback, useContext } from "react"; import { createContext, useCallback, useContext, useMemo } from "react";
import { debounce } from "../libs/utils";
const SettingContext = createContext({ const SettingContext = createContext({
setting: null, setting: null,
updateSetting: async () => {}, updateSetting: async () => {},
reloadSetting: async () => {},
}); });
export function SettingProvider({ children }) { export function SettingProvider({ children }) {
const { data, update } = useStorage(STOKEY_SETTING, DEFAULT_SETTING); const { data, update, reload } = useStorage(STOKEY_SETTING, DEFAULT_SETTING);
const { const {
sync: { settingUpdateAt }, sync: { settingUpdateAt },
updateSync, updateSync,
} = useSync(); } = useSync();
const syncSetting = useMemo(
() =>
debounce(() => {
trySyncSetting();
}, [2000]),
[]
);
const updateSetting = useCallback( const updateSetting = useCallback(
async (obj) => { async (obj) => {
const updateAt = settingUpdateAt ? Date.now() : 0; const updateAt = settingUpdateAt ? Date.now() : 0;
await update(obj); await update(obj);
await updateSync({ settingUpdateAt: updateAt }); await updateSync({ settingUpdateAt: updateAt });
trySyncSetting(); syncSetting();
}, },
[settingUpdateAt, update, updateSync] [settingUpdateAt, update, updateSync, syncSetting]
); );
return ( return (
@@ -31,6 +41,7 @@ export function SettingProvider({ children }) {
value={{ value={{
setting: data, setting: data,
updateSetting, updateSetting,
reloadSetting: reload,
}} }}
> >
{children} {children}

View File

@@ -25,16 +25,20 @@ export function useStorage(key, defaultVal = null) {
await storage.del(key); await storage.del(key);
}, [key]); }, [key]);
useEffect(() => { const reload = useCallback(async () => {
(async () => { const val = await storage.getObj(key);
const val = await storage.getObj(key); if (val) {
if (val) { setData(val);
setData(val); } else if (defaultVal) {
} else if (defaultVal) { await storage.setObj(key, defaultVal);
await storage.setObj(key, defaultVal); }
}
})();
}, [key, defaultVal]); }, [key, defaultVal]);
return { data, save, update, remove }; useEffect(() => {
(async () => {
await reload();
})();
}, [reload]);
return { data, save, update, remove, reload };
} }

View File

@@ -1,4 +1,4 @@
import { DEFAULT_SUBRULES_LIST } from "../config"; import { DEFAULT_SUBRULES_LIST, DEFAULT_OW_RULE } from "../config";
import { useSetting } from "./Setting"; import { useSetting } from "./Setting";
import { useCallback, useEffect, useMemo, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import { loadOrFetchSubRules } from "../libs/subRules"; import { loadOrFetchSubRules } from "../libs/subRules";
@@ -79,3 +79,21 @@ export function useSubRules() {
loading, loading,
}; };
} }
/**
* 覆写订阅规则
* @returns
*/
export function useOwSubRule() {
const { setting, updateSetting } = useSetting();
const { owSubrule = DEFAULT_OW_RULE } = setting;
const updateOwSubrule = useCallback(
async (obj) => {
await updateSetting({ owSubrule: { ...owSubrule, ...obj } });
},
[owSubrule, updateSetting]
);
return { owSubrule, updateOwSubrule };
}

View File

@@ -7,6 +7,7 @@ import {
MSG_FETCH_CLEAR, MSG_FETCH_CLEAR,
CACHE_NAME, CACHE_NAME,
OPT_TRANS_MICROSOFT, OPT_TRANS_MICROSOFT,
OPT_TRANS_DEEPL,
OPT_TRANS_OPENAI, OPT_TRANS_OPENAI,
DEFAULT_FETCH_INTERVAL, DEFAULT_FETCH_INTERVAL,
DEFAULT_FETCH_LIMIT, DEFAULT_FETCH_LIMIT,
@@ -67,9 +68,11 @@ const newCacheReq = async (request) => {
*/ */
const fetchApi = async ({ input, init = {}, translator, token }) => { const fetchApi = async ({ input, init = {}, translator, token }) => {
if (translator === OPT_TRANS_MICROSOFT) { if (translator === OPT_TRANS_MICROSOFT) {
init.headers["Authorization"] = `Bearer ${token}`; init.headers["Authorization"] = `Bearer ${token}`; // Microsoft
} else if (translator === OPT_TRANS_DEEPL) {
init.headers["Authorization"] = `DeepL-Auth-Key ${token}`; // DeepL
} else if (translator === OPT_TRANS_OPENAI) { } else if (translator === OPT_TRANS_OPENAI) {
init.headers["Authorization"] = `Bearer ${token}`; // // OpenAI init.headers["Authorization"] = `Bearer ${token}`; // OpenAI
init.headers["api-key"] = token; // Azure OpenAI init.headers["api-key"] = token; // Azure OpenAI
} }

View File

@@ -1,20 +1,31 @@
import { fetchGM } from "./fetch"; import { fetchGM } from "./fetch";
import { genEventName } from "./utils";
const MSG_GM_xmlHttpRequest = "xmlHttpRequest";
const MSG_GM_setValue = "setValue";
const MSG_GM_getValue = "getValue";
const MSG_GM_deleteValue = "deleteValue";
const MSG_GM_info = "info";
/** /**
* 注入页面的脚本请求并接受GM接口信息 * 注入页面的脚本请求并接受GM接口信息
* @param {*} param0 * @param {*} param0
*/ */
export const injectScript = (ping) => { export const injectScript = (ping) => {
const MSG_GM_xmlHttpRequest = "xmlHttpRequest"; window.APP_INFO = {
const MSG_GM_setValue = "setValue"; name: process.env.REACT_APP_NAME,
const MSG_GM_getValue = "getValue"; version: process.env.REACT_APP_VERSION,
const MSG_GM_deleteValue = "deleteValue"; eventName: ping,
const MSG_GM_info = "info"; };
let GM_info; };
/**
* 适配GM脚本
*/
export const adaptScript = (ping) => {
const promiseGM = (action, args, timeout = 5000) => const promiseGM = (action, args, timeout = 5000) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
const pong = btoa(Math.random()).slice(3, 11); const pong = genEventName();
const handleEvent = (e) => { const handleEvent = (e) => {
window.removeEventListener(pong, handleEvent); window.removeEventListener(pong, handleEvent);
const { data, error } = e.detail; const { data, error } = e.detail;
@@ -41,14 +52,13 @@ export const injectScript = (ping) => {
setValue: (key, val) => promiseGM(MSG_GM_setValue, { key, val }), setValue: (key, val) => promiseGM(MSG_GM_setValue, { key, val }),
getValue: (key) => promiseGM(MSG_GM_getValue, { key }), getValue: (key) => promiseGM(MSG_GM_getValue, { key }),
deleteValue: (key) => promiseGM(MSG_GM_deleteValue, { key }), deleteValue: (key) => promiseGM(MSG_GM_deleteValue, { key }),
getInfo: () => { getInfo: async () => {
if (GM_info) { if (!window.GM_info) {
return GM_info; window.GM_info = await promiseGM(MSG_GM_info);
} }
return promiseGM(MSG_GM_info); return window.GM_info;
}, },
}; };
window.APP_NAME = process.env.REACT_APP_NAME;
}; };
/** /**
@@ -56,11 +66,6 @@ export const injectScript = (ping) => {
* @param {*} param0 * @param {*} param0
*/ */
export const handlePing = async (e) => { export const handlePing = async (e) => {
const MSG_GM_xmlHttpRequest = "xmlHttpRequest";
const MSG_GM_setValue = "setValue";
const MSG_GM_getValue = "getValue";
const MSG_GM_deleteValue = "deleteValue";
const MSG_GM_info = "info";
const { action, args, pong } = e.detail; const { action, args, pong } = e.detail;
let res; let res;
try { try {

View File

@@ -1,12 +1,14 @@
import { matchValue, type, isMatch } from "./utils"; import { matchValue, type, isMatch } from "./utils";
import { import {
GLOBAL_KEY, GLOBAL_KEY,
REMAIN_KEY,
OPT_TRANS_ALL, OPT_TRANS_ALL,
OPT_STYLE_ALL, OPT_STYLE_ALL,
OPT_LANGS_FROM, OPT_LANGS_FROM,
OPT_LANGS_TO, OPT_LANGS_TO,
GLOBLA_RULE, GLOBLA_RULE,
DEFAULT_SUBRULES_LIST, DEFAULT_SUBRULES_LIST,
DEFAULT_OW_RULE,
} from "../config"; } from "../config";
import { loadOrFetchSubRules } from "./subRules"; import { loadOrFetchSubRules } from "./subRules";
@@ -19,14 +21,35 @@ import { loadOrFetchSubRules } from "./subRules";
export const matchRule = async ( export const matchRule = async (
rules, rules,
href, href,
{ injectRules = true, subrulesList = DEFAULT_SUBRULES_LIST } {
injectRules = true,
subrulesList = DEFAULT_SUBRULES_LIST,
owSubrule = DEFAULT_OW_RULE,
}
) => { ) => {
rules = [...rules]; rules = [...rules];
if (injectRules) { if (injectRules) {
try { try {
const selectedSub = subrulesList.find((item) => item.selected); const selectedSub = subrulesList.find((item) => item.selected);
if (selectedSub?.url) { if (selectedSub?.url) {
const subRules = await loadOrFetchSubRules(selectedSub.url); const mixRule = {};
Object.entries(owSubrule)
.filter(([key, val]) => {
if (
owSubrule.textStyle === REMAIN_KEY &&
(key === "bgColor" || key === "textDiyStyle")
) {
return false;
}
return val !== REMAIN_KEY;
})
.forEach(([key, val]) => {
mixRule[key] = val;
});
const subRules = (await loadOrFetchSubRules(selectedSub.url)).map(
(item) => ({ ...item, ...mixRule })
);
rules.splice(-1, 0, ...subRules); rules.splice(-1, 0, ...subRules);
} }
} catch (err) { } catch (err) {
@@ -39,8 +62,9 @@ export const matchRule = async (
); );
const globalRule = const globalRule =
rules.find((r) => r.pattern.split(",").some((p) => p.trim() === "*")) || rules.find((r) =>
GLOBLA_RULE; r.pattern.split(",").some((p) => p.trim() === GLOBAL_KEY)
) || GLOBLA_RULE;
if (!rule) { if (!rule) {
return globalRule; return globalRule;
@@ -52,6 +76,8 @@ export const matchRule = async (
GLOBLA_RULE.selector; GLOBLA_RULE.selector;
rule.bgColor = rule?.bgColor?.trim() || globalRule?.bgColor?.trim(); rule.bgColor = rule?.bgColor?.trim() || globalRule?.bgColor?.trim();
rule.textDiyStyle =
rule?.textDiyStyle?.trim() || globalRule?.textDiyStyle?.trim();
["translator", "fromLang", "toLang", "textStyle", "transOpen"].forEach( ["translator", "fromLang", "toLang", "textStyle", "transOpen"].forEach(
(key) => { (key) => {
@@ -99,10 +125,12 @@ export const checkRules = (rules) => {
textStyle, textStyle,
transOpen, transOpen,
bgColor, bgColor,
textDiyStyle,
}) => ({ }) => ({
pattern: pattern.trim(), pattern: pattern.trim(),
selector: type(selector) === "string" ? selector : "", selector: type(selector) === "string" ? selector : "",
bgColor: type(bgColor) === "string" ? bgColor : "", bgColor: type(bgColor) === "string" ? bgColor : "",
textDiyStyle: type(textDiyStyle) === "string" ? textDiyStyle : "",
translator: matchValue([GLOBAL_KEY, ...OPT_TRANS_ALL], translator), translator: matchValue([GLOBAL_KEY, ...OPT_TRANS_ALL], translator),
fromLang: matchValue([GLOBAL_KEY, ...fromLangs], fromLang), fromLang: matchValue([GLOBAL_KEY, ...fromLangs], fromLang),
toLang: matchValue([GLOBAL_KEY, ...toLangs], toLang), toLang: matchValue([GLOBAL_KEY, ...toLangs], toLang),

View File

@@ -43,6 +43,7 @@ const syncSetting = async (isBg = false) => {
settingSyncAt: res.updateAt, settingSyncAt: res.updateAt,
}); });
await setSetting(res.value); await setSetting(res.value);
return res.value;
} else { } else {
await updateSync({ settingSyncAt: res.updateAt }); await updateSync({ settingSyncAt: res.updateAt });
} }
@@ -50,7 +51,7 @@ const syncSetting = async (isBg = false) => {
export const trySyncSetting = async (isBg = false) => { export const trySyncSetting = async (isBg = false) => {
try { try {
await syncSetting(isBg); return await syncSetting(isBg);
} catch (err) { } catch (err) {
console.log("[sync setting]", err); console.log("[sync setting]", err);
} }
@@ -84,6 +85,7 @@ const syncRules = async (isBg = false) => {
rulesSyncAt: res.updateAt, rulesSyncAt: res.updateAt,
}); });
await setRules(res.value); await setRules(res.value);
return res.value;
} else { } else {
await updateSync({ rulesSyncAt: res.updateAt }); await updateSync({ rulesSyncAt: res.updateAt });
} }
@@ -91,7 +93,7 @@ const syncRules = async (isBg = false) => {
export const trySyncRules = async (isBg = false) => { export const trySyncRules = async (isBg = false) => {
try { try {
await syncRules(isBg); return await syncRules(isBg);
} catch (err) { } catch (err) {
console.log("[sync user rules]", err); console.log("[sync user rules]", err);
} }
@@ -118,11 +120,9 @@ export const syncShareRules = async ({ rules, syncUrl, syncKey }) => {
* @returns * @returns
*/ */
export const syncSettingAndRules = async (isBg = false) => { export const syncSettingAndRules = async (isBg = false) => {
await syncSetting(isBg); return [await syncSetting(isBg), await syncRules(isBg)];
await syncRules(isBg);
}; };
export const trySyncSettingAndRules = async (isBg = false) => { export const trySyncSettingAndRules = async (isBg = false) => {
await trySyncSetting(isBg); return [await trySyncSetting(isBg), await trySyncRules(isBg)];
await trySyncRules(isBg);
}; };

View File

@@ -3,7 +3,6 @@ import {
APP_LCNAME, APP_LCNAME,
TRANS_MIN_LENGTH, TRANS_MIN_LENGTH,
TRANS_MAX_LENGTH, TRANS_MAX_LENGTH,
EVENT_KISS,
MSG_TRANS_CURRULE, MSG_TRANS_CURRULE,
OPT_STYLE_DASHLINE, OPT_STYLE_DASHLINE,
OPT_STYLE_FUZZY, OPT_STYLE_FUZZY,
@@ -11,7 +10,7 @@ import {
} from "../config"; } from "../config";
import Content from "../views/Content"; import Content from "../views/Content";
import { updateFetchPool, clearFetchPool } from "./fetch"; import { updateFetchPool, clearFetchPool } from "./fetch";
import { debounce } from "./utils"; import { debounce, genEventName } from "./utils";
/** /**
* 翻译类 * 翻译类
@@ -37,6 +36,7 @@ export class Translator {
"script", "script",
"iframe", "iframe",
]; ];
_eventName = genEventName();
// 显示 // 显示
_interseObserver = new IntersectionObserver( _interseObserver = new IntersectionObserver(
@@ -105,6 +105,10 @@ export class Translator {
return this._setting; return this._setting;
} }
get eventName() {
return this._eventName;
}
get rule() { get rule() {
// console.log("get rule", this._rule); // console.log("get rule", this._rule);
return this._rule; return this._rule;
@@ -115,8 +119,9 @@ export class Translator {
this._rule = rule; this._rule = rule;
// 广播消息 // 广播消息
const eventName = this._eventName;
window.dispatchEvent( window.dispatchEvent(
new CustomEvent(EVENT_KISS, { new CustomEvent(eventName, {
detail: { detail: {
action: MSG_TRANS_CURRULE, action: MSG_TRANS_CURRULE,
args: rule, args: rule,

View File

@@ -116,3 +116,9 @@ export const sha256 = async (text, salt) => {
.map((b) => b.toString(16).padStart(2, "0")) .map((b) => b.toString(16).padStart(2, "0"))
.join(""); .join("");
}; };
/**
* 生成随机事件名称
* @returns
*/
export const genEventName = () => btoa(Math.random()).slice(3, 11);

View File

@@ -10,11 +10,11 @@ import {
} from "./libs/storage"; } from "./libs/storage";
import { Translator } from "./libs/translator"; import { Translator } from "./libs/translator";
import { trySyncAllSubRules } from "./libs/subRules"; import { trySyncAllSubRules } from "./libs/subRules";
import { isGm } from "./libs/client";
import { MSG_TRANS_TOGGLE, MSG_TRANS_PUTRULE } from "./config"; import { MSG_TRANS_TOGGLE, MSG_TRANS_PUTRULE } from "./config";
import { isIframe } from "./libs/iframe"; import { isIframe } from "./libs/iframe";
import { handlePing, injectScript } from "./libs/gm"; import { handlePing, injectScript } from "./libs/gm";
import { matchRule } from "./libs/rules"; import { matchRule } from "./libs/rules";
import { genEventName } from "./libs/utils";
/** /**
* 入口函数 * 入口函数
@@ -28,9 +28,12 @@ const init = async () => {
) { ) {
if (GM?.info?.script?.grant?.includes("unsafeWindow")) { if (GM?.info?.script?.grant?.includes("unsafeWindow")) {
unsafeWindow.GM = GM; unsafeWindow.GM = GM;
unsafeWindow.APP_NAME = process.env.REACT_APP_NAME; unsafeWindow.APP_INFO = {
name: process.env.REACT_APP_NAME,
version: process.env.REACT_APP_VERSION,
};
} else { } else {
const ping = btoa(Math.random()).slice(3, 11); const ping = genEventName();
window.addEventListener(ping, handlePing); window.addEventListener(ping, handlePing);
// window.eval(`(${injectScript})("${ping}")`); // eslint-disable-line // window.eval(`(${injectScript})("${ping}")`); // eslint-disable-line
const script = document.createElement("script"); const script = document.createElement("script");
@@ -88,28 +91,6 @@ const init = async () => {
</React.StrictMode> </React.StrictMode>
); );
// 注册菜单
if (isGm) {
try {
GM.registerMenuCommand(
"Toggle Translate",
(event) => {
translator.toggle();
},
"Q"
);
GM.registerMenuCommand(
"Toggle Style",
(event) => {
translator.toggleStyle();
},
"C"
);
} catch (err) {
console.log("[registerMenuCommand]", err);
}
}
// 同步订阅规则 // 同步订阅规则
trySyncAllSubRules(setting); trySyncAllSubRules(setting);
}; };

View File

@@ -11,13 +11,15 @@ import { useEffect, useState, useMemo, useCallback } from "react";
import { SettingProvider } from "../../hooks/Setting"; import { SettingProvider } from "../../hooks/Setting";
import Popup from "../Popup"; import Popup from "../Popup";
import { debounce } from "../../libs/utils"; import { debounce } from "../../libs/utils";
import * as shortcut from "@violentmonkey/shortcut";
import { isGm } from "../../libs/client";
export default function Action({ translator, fab }) { export default function Action({ translator, fab }) {
const fabWidth = 40; const fabWidth = 40;
const [showPopup, setShowPopup] = useState(false); const [showPopup, setShowPopup] = useState(false);
const [windowSize, setWindowSize] = useState({ const [windowSize, setWindowSize] = useState({
w: document.documentElement.clientWidth, w: window.innerWidth,
h: document.documentElement.clientHeight, h: window.innerHeight,
}); });
const [moved, setMoved] = useState(false); const [moved, setMoved] = useState(false);
@@ -25,8 +27,8 @@ export default function Action({ translator, fab }) {
() => () =>
debounce(() => { debounce(() => {
setWindowSize({ setWindowSize({
w: document.documentElement.clientWidth, w: window.innerWidth,
h: document.documentElement.clientHeight, h: window.innerHeight,
}); });
}), }),
[] []
@@ -44,6 +46,73 @@ export default function Action({ translator, fab }) {
setMoved(true); setMoved(true);
}, []); }, []);
useEffect(() => {
// 注册快捷键
shortcut.register("a-q", () => {
translator.toggle();
setShowPopup(false);
});
shortcut.register("a-c", () => {
translator.toggleStyle();
setShowPopup(false);
});
shortcut.register("a-k", () => {
setShowPopup((pre) => !pre);
});
return () => {
shortcut.disable();
};
}, [translator]);
useEffect(() => {
// 注册菜单
const menuCommandIds = [];
if (isGm) {
try {
menuCommandIds.push(
GM.registerMenuCommand(
"Toggle Translate",
(event) => {
translator.toggle();
setShowPopup(false);
},
"Q"
),
GM.registerMenuCommand(
"Toggle Style",
(event) => {
translator.toggleStyle();
setShowPopup(false);
},
"C"
),
GM.registerMenuCommand(
"Open Menu",
(event) => {
setShowPopup((pre) => !pre);
},
"K"
)
);
} catch (err) {
console.log("[registerMenuCommand]", err);
}
}
return () => {
if (isGm) {
try {
menuCommandIds.forEach((id) => {
GM.unregisterMenuCommand(id);
});
} catch (err) {
//
}
}
};
}, [translator]);
useEffect(() => { useEffect(() => {
window.addEventListener("resize", handleWindowResize); window.addEventListener("resize", handleWindowResize);
return () => { return () => {
@@ -53,6 +122,7 @@ export default function Action({ translator, fab }) {
useEffect(() => { useEffect(() => {
window.addEventListener("click", handleWindowClick); window.addEventListener("click", handleWindowClick);
return () => { return () => {
window.removeEventListener("click", handleWindowClick); window.removeEventListener("click", handleWindowClick);
}; };

View File

@@ -1,4 +1,4 @@
import { useMemo, useState, useEffect } from "react"; import { useState, useEffect } from "react";
import LoadingIcon from "./LoadingIcon"; import LoadingIcon from "./LoadingIcon";
import { import {
OPT_STYLE_LINE, OPT_STYLE_LINE,
@@ -6,26 +6,99 @@ import {
OPT_STYLE_DASHLINE, OPT_STYLE_DASHLINE,
OPT_STYLE_WAVYLINE, OPT_STYLE_WAVYLINE,
OPT_STYLE_FUZZY, OPT_STYLE_FUZZY,
OPT_STYLE_HIGHTLIGHT, OPT_STYLE_HIGHLIGHT,
OPT_STYLE_DIY,
DEFAULT_COLOR, DEFAULT_COLOR,
EVENT_KISS,
MSG_TRANS_CURRULE, MSG_TRANS_CURRULE,
TRANS_NEWLINE_LENGTH,
} from "../../config"; } from "../../config";
import { useTranslate } from "../../hooks/Translate"; import { useTranslate } from "../../hooks/Translate";
import styled from "styled-components";
const LineSpan = styled.span`
opacity: 0.6;
-webkit-opacity: 0.6;
text-decoration-line: underline;
text-decoration-style: ${(props) => props.$lineStyle};
text-decoration-color: ${(props) => props.$lineColor};
text-decoration-thickness: 2px;
text-underline-offset: 0.3em;
-webkit-text-decoration-line: underline;
-webkit-text-decoration-style: ${(props) => props.$lineStyle};
-webkit-text-decoration-color: ${(props) => props.$lineColor};
-webkit-text-decoration-thickness: 2px;
-webkit-text-underline-offset: 0.3em;
&:hover {
opacity: 1;
-webkit-opacity: 1;
}
`;
const FuzzySpan = styled.span`
filter: blur(5px);
-webkit-filter: blur(5px);
&:hover {
filter: none;
-webkit-filter: none;
}
`;
const HighlightSpan = styled.span`
color: #fff;
background-color: ${(props) => props.$bgColor};
`;
const DiySpan = styled.span`
${(props) => props.$diyStyle}
`;
function StyledSpan({ textStyle, textDiyStyle, bgColor, children }) {
switch (textStyle) {
case OPT_STYLE_LINE: // 下划线
return (
<LineSpan $lineStyle="solid" $lineColor={bgColor}>
{children}
</LineSpan>
);
case OPT_STYLE_DOTLINE: // 点状线
return (
<LineSpan $lineStyle="dotted" $lineColor={bgColor}>
{children}
</LineSpan>
);
case OPT_STYLE_DASHLINE: // 虚线
return (
<LineSpan $lineStyle="dashed" $lineColor={bgColor}>
{children}
</LineSpan>
);
case OPT_STYLE_WAVYLINE: // 波浪线
return (
<LineSpan $lineStyle="wavy" $lineColor={bgColor}>
{children}
</LineSpan>
);
case OPT_STYLE_FUZZY: // 模糊
return <FuzzySpan>{children}</FuzzySpan>;
case OPT_STYLE_HIGHLIGHT: // 高亮
return (
<HighlightSpan $bgColor={bgColor || DEFAULT_COLOR}>
{children}
</HighlightSpan>
);
case OPT_STYLE_DIY: // 自定义
return <DiySpan $diyStyle={textDiyStyle}>{children}</DiySpan>;
default:
return <span>{children}</span>;
}
}
export default function Content({ q, translator }) { export default function Content({ q, translator }) {
const [rule, setRule] = useState(translator.rule); const [rule, setRule] = useState(translator.rule);
const [hover, setHover] = useState(false);
const { text, sameLang, loading } = useTranslate(q, rule, translator.setting); const { text, sameLang, loading } = useTranslate(q, rule, translator.setting);
const { textStyle, bgColor } = rule; const { textStyle, bgColor = "", textDiyStyle = "" } = rule;
const handleMouseEnter = () => { const { newlineLength = TRANS_NEWLINE_LENGTH } = translator.setting;
setHover(true);
};
const handleMouseLeave = () => {
setHover(false);
};
const handleKissEvent = (e) => { const handleKissEvent = (e) => {
const { action, args } = e.detail; const { action, args } = e.detail;
@@ -34,60 +107,20 @@ export default function Content({ q, translator }) {
setRule(args); setRule(args);
break; break;
default: default:
// console.log(`[popup] kissEvent action skip: ${action}`);
} }
}; };
useEffect(() => { useEffect(() => {
window.addEventListener(EVENT_KISS, handleKissEvent); window.addEventListener(translator.eventName, handleKissEvent);
return () => { return () => {
window.removeEventListener(EVENT_KISS, handleKissEvent); window.removeEventListener(translator.eventName, handleKissEvent);
}; };
}, []); }, [translator.eventName]);
const style = useMemo(() => {
const lineColor = bgColor || "";
const underlineStyle = (st) => ({
opacity: hover ? 1 : 0.6,
textDecorationLine: "underline",
textDecorationColor: lineColor,
textDecorationStyle: st,
textDecorationThickness: "2px",
textUnderlineOffset: "0.3em",
WebkittextDecorationLine: "underline",
WebkittextDecorationColor: lineColor,
WebkittextDecorationStyle: st,
WebkittextDecorationThickness: "2px",
WebkittextTextUnderlineOffset: "0.3em",
});
switch (textStyle) {
case OPT_STYLE_LINE: // 下划线
return underlineStyle("solid");
case OPT_STYLE_DOTLINE: // 点状线
return underlineStyle("dotted");
case OPT_STYLE_DASHLINE: // 虚线
return underlineStyle("dashed");
case OPT_STYLE_WAVYLINE: // 波浪线
return underlineStyle("wavy");
case OPT_STYLE_FUZZY: // 模糊
return {
filter: hover ? "none" : "blur(5px)",
transition: "filter 0.2s ease-in-out",
};
case OPT_STYLE_HIGHTLIGHT: // 高亮
return {
color: "#FFF",
backgroundColor: bgColor || DEFAULT_COLOR,
};
default:
return {};
}
}, [textStyle, hover, bgColor]);
if (loading) { if (loading) {
return ( return (
<> <>
{q.length > 40 ? <br /> : " "} {q.length > newlineLength ? <br /> : " "}
<LoadingIcon /> <LoadingIcon />
</> </>
); );
@@ -96,14 +129,14 @@ export default function Content({ q, translator }) {
if (text && !sameLang) { if (text && !sameLang) {
return ( return (
<> <>
{q.length > 40 ? <br /> : " "} {q.length > newlineLength ? <br /> : " "}
<span <StyledSpan
style={style} textStyle={textStyle}
onMouseEnter={handleMouseEnter} textDiyStyle={textDiyStyle}
onMouseLeave={handleMouseLeave} bgColor={bgColor}
> >
{text} {text}
</span> </StyledSpan>
</> </>
); );
} }

View File

@@ -0,0 +1,175 @@
import Box from "@mui/material/Box";
import Stack from "@mui/material/Stack";
import TextField from "@mui/material/TextField";
import {
GLOBAL_KEY,
REMAIN_KEY,
OPT_LANGS_FROM,
OPT_LANGS_TO,
OPT_TRANS_ALL,
OPT_STYLE_ALL,
OPT_STYLE_DIY,
OPT_STYLE_USE_COLOR,
} from "../../config";
import { useI18n } from "../../hooks/I18n";
import MenuItem from "@mui/material/MenuItem";
import Grid from "@mui/material/Grid";
import { useOwSubRule } from "../../hooks/SubRules";
export default function OwSubRule() {
const i18n = useI18n();
const { owSubrule, updateOwSubrule } = useOwSubRule();
const handleChange = (e) => {
e.preventDefault();
const { name, value } = e.target;
updateOwSubrule({ [name]: value });
};
const {
translator,
fromLang,
toLang,
textStyle,
transOpen,
bgColor,
textDiyStyle,
} = owSubrule;
const RemainItem = (
<MenuItem key={REMAIN_KEY} value={REMAIN_KEY}>
{i18n("remain_unchanged")}
</MenuItem>
);
const GlobalItem = (
<MenuItem key={GLOBAL_KEY} value={GLOBAL_KEY}>
{GLOBAL_KEY}
</MenuItem>
);
return (
<Stack spacing={2}>
<Box>
<Grid container spacing={2} columns={12}>
<Grid item xs={12} sm={6} md={3} lg={2}>
<TextField
select
size="small"
fullWidth
name="transOpen"
value={transOpen}
label={i18n("translate_switch")}
onChange={handleChange}
>
{RemainItem}
{GlobalItem}
<MenuItem value={"true"}>{i18n("default_enabled")}</MenuItem>
<MenuItem value={"false"}>{i18n("default_disabled")}</MenuItem>
</TextField>
</Grid>
<Grid item xs={12} sm={6} md={3} lg={2}>
<TextField
select
size="small"
fullWidth
name="translator"
value={translator}
label={i18n("translate_service")}
onChange={handleChange}
>
{RemainItem}
{GlobalItem}
{OPT_TRANS_ALL.map((item) => (
<MenuItem key={item} value={item}>
{item}
</MenuItem>
))}
</TextField>
</Grid>
<Grid item xs={12} sm={6} md={3} lg={2}>
<TextField
select
size="small"
fullWidth
name="fromLang"
value={fromLang}
label={i18n("from_lang")}
onChange={handleChange}
>
{RemainItem}
{GlobalItem}
{OPT_LANGS_FROM.map(([lang, name]) => (
<MenuItem key={lang} value={lang}>
{name}
</MenuItem>
))}
</TextField>
</Grid>
<Grid item xs={12} sm={6} md={3} lg={2}>
<TextField
select
size="small"
fullWidth
name="toLang"
value={toLang}
label={i18n("to_lang")}
onChange={handleChange}
>
{RemainItem}
{GlobalItem}
{OPT_LANGS_TO.map(([lang, name]) => (
<MenuItem key={lang} value={lang}>
{name}
</MenuItem>
))}
</TextField>
</Grid>
<Grid item xs={12} sm={6} md={3} lg={2}>
<TextField
select
size="small"
fullWidth
name="textStyle"
value={textStyle}
label={i18n("text_style")}
onChange={handleChange}
>
{RemainItem}
{GlobalItem}
{OPT_STYLE_ALL.map((item) => (
<MenuItem key={item} value={item}>
{i18n(item)}
</MenuItem>
))}
</TextField>
</Grid>
{OPT_STYLE_USE_COLOR.includes(textStyle) && (
<Grid item xs={12} sm={6} md={3} lg={2}>
<TextField
size="small"
fullWidth
name="bgColor"
value={bgColor}
label={i18n("bg_color")}
onChange={handleChange}
/>
</Grid>
)}
</Grid>
</Box>
{textStyle === OPT_STYLE_DIY && (
<TextField
size="small"
label={i18n("diy_style")}
helperText={i18n("diy_style_helper")}
name="textDiyStyle"
value={textDiyStyle}
onChange={handleChange}
multiline
/>
)}
</Stack>
);
}

View File

@@ -11,6 +11,8 @@ import {
OPT_LANGS_TO, OPT_LANGS_TO,
OPT_TRANS_ALL, OPT_TRANS_ALL,
OPT_STYLE_ALL, OPT_STYLE_ALL,
OPT_STYLE_DIY,
OPT_STYLE_USE_COLOR,
} from "../../config"; } from "../../config";
import { useState, useRef, useEffect, useMemo } from "react"; import { useState, useRef, useEffect, useMemo } from "react";
import { useI18n } from "../../hooks/I18n"; import { useI18n } from "../../hooks/I18n";
@@ -42,9 +44,13 @@ import { useAlert } from "../../hooks/Alert";
import { syncShareRules } from "../../libs/sync"; import { syncShareRules } from "../../libs/sync";
import { debounce } from "../../libs/utils"; import { debounce } from "../../libs/utils";
import { delSubRules, getSyncWithDefault } from "../../libs/storage"; import { delSubRules, getSyncWithDefault } from "../../libs/storage";
import OwSubRule from "./OwSubRule";
function RuleFields({ rule, rules, setShow, setKeyword }) { function RuleFields({ rule, rules, setShow, setKeyword }) {
const initFormValues = rule || { ...DEFAULT_RULE, transOpen: "true" }; const initFormValues = rule || {
...DEFAULT_RULE,
transOpen: "true",
};
const editMode = !!rule; const editMode = !!rule;
const i18n = useI18n(); const i18n = useI18n();
@@ -60,6 +66,7 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
textStyle, textStyle,
transOpen, transOpen,
bgColor, bgColor,
textDiyStyle,
} = formValues; } = formValues;
const hasSamePattern = (str) => { const hasSamePattern = (str) => {
@@ -134,7 +141,7 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
} }
}; };
const globalItem = rule?.pattern !== "*" && ( const GlobalItem = rule?.pattern !== "*" && (
<MenuItem key={GLOBAL_KEY} value={GLOBAL_KEY}> <MenuItem key={GLOBAL_KEY} value={GLOBAL_KEY}>
{GLOBAL_KEY} {GLOBAL_KEY}
</MenuItem> </MenuItem>
@@ -181,7 +188,7 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
disabled={disabled} disabled={disabled}
onChange={handleChange} onChange={handleChange}
> >
{globalItem} {GlobalItem}
<MenuItem value={"true"}>{i18n("default_enabled")}</MenuItem> <MenuItem value={"true"}>{i18n("default_enabled")}</MenuItem>
<MenuItem value={"false"}>{i18n("default_disabled")}</MenuItem> <MenuItem value={"false"}>{i18n("default_disabled")}</MenuItem>
</TextField> </TextField>
@@ -197,7 +204,7 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
disabled={disabled} disabled={disabled}
onChange={handleChange} onChange={handleChange}
> >
{globalItem} {GlobalItem}
{OPT_TRANS_ALL.map((item) => ( {OPT_TRANS_ALL.map((item) => (
<MenuItem key={item} value={item}> <MenuItem key={item} value={item}>
{item} {item}
@@ -216,7 +223,7 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
disabled={disabled} disabled={disabled}
onChange={handleChange} onChange={handleChange}
> >
{globalItem} {GlobalItem}
{OPT_LANGS_FROM.map(([lang, name]) => ( {OPT_LANGS_FROM.map(([lang, name]) => (
<MenuItem key={lang} value={lang}> <MenuItem key={lang} value={lang}>
{name} {name}
@@ -235,7 +242,7 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
disabled={disabled} disabled={disabled}
onChange={handleChange} onChange={handleChange}
> >
{globalItem} {GlobalItem}
{OPT_LANGS_TO.map(([lang, name]) => ( {OPT_LANGS_TO.map(([lang, name]) => (
<MenuItem key={lang} value={lang}> <MenuItem key={lang} value={lang}>
{name} {name}
@@ -254,7 +261,7 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
disabled={disabled} disabled={disabled}
onChange={handleChange} onChange={handleChange}
> >
{globalItem} {GlobalItem}
{OPT_STYLE_ALL.map((item) => ( {OPT_STYLE_ALL.map((item) => (
<MenuItem key={item} value={item}> <MenuItem key={item} value={item}>
{i18n(item)} {i18n(item)}
@@ -262,20 +269,35 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
))} ))}
</TextField> </TextField>
</Grid> </Grid>
<Grid item xs={12} sm={6} md={3} lg={2}> {OPT_STYLE_USE_COLOR.includes(textStyle) && (
<TextField <Grid item xs={12} sm={6} md={3} lg={2}>
size="small" <TextField
fullWidth size="small"
name="bgColor" fullWidth
value={bgColor} name="bgColor"
label={i18n("bg_color")} value={bgColor}
disabled={disabled} label={i18n("bg_color")}
onChange={handleChange} disabled={disabled}
/> onChange={handleChange}
</Grid> />
</Grid>
)}
</Grid> </Grid>
</Box> </Box>
{textStyle === OPT_STYLE_DIY && (
<TextField
size="small"
label={i18n("diy_style")}
helperText={i18n("diy_style_helper")}
name="textDiyStyle"
value={textDiyStyle}
disabled={disabled}
onChange={handleChange}
multiline
/>
)}
{rules && {rules &&
(editMode ? ( (editMode ? (
// 编辑 // 编辑
@@ -797,6 +819,7 @@ export default function Rules() {
<Tabs value={activeTab} onChange={handleTabChange}> <Tabs value={activeTab} onChange={handleTabChange}>
<Tab label={i18n("personal_rules")} /> <Tab label={i18n("personal_rules")} />
<Tab label={i18n("subscribe_rules")} /> <Tab label={i18n("subscribe_rules")} />
<Tab label={i18n("overwrite_subscribe_rules")} />
</Tabs> </Tabs>
</Box> </Box>
<div hidden={activeTab !== 0}> <div hidden={activeTab !== 0}>
@@ -805,6 +828,7 @@ export default function Rules() {
<div hidden={activeTab !== 1}> <div hidden={activeTab !== 1}>
{activeTab === 1 && <SubRules subRules={subRules} />} {activeTab === 1 && <SubRules subRules={subRules} />}
</div> </div>
<div hidden={activeTab !== 2}>{activeTab === 2 && <OwSubRule />}</div>
</Stack> </Stack>
</Box> </Box>
); );

View File

@@ -5,10 +5,11 @@ import TextField from "@mui/material/TextField";
import MenuItem from "@mui/material/MenuItem"; import MenuItem from "@mui/material/MenuItem";
import FormControl from "@mui/material/FormControl"; import FormControl from "@mui/material/FormControl";
import Select from "@mui/material/Select"; import Select from "@mui/material/Select";
import Link from "@mui/material/Link";
import { useSetting } from "../../hooks/Setting"; import { useSetting } from "../../hooks/Setting";
import { limitNumber } from "../../libs/utils"; import { limitNumber } from "../../libs/utils";
import { useI18n } from "../../hooks/I18n"; import { useI18n } from "../../hooks/I18n";
import { UI_LANGS } from "../../config"; import { UI_LANGS, URL_KISS_PROXY, TRANS_NEWLINE_LENGTH } from "../../config";
export default function Settings() { export default function Settings() {
const i18n = useI18n(); const i18n = useI18n();
@@ -30,6 +31,9 @@ export default function Settings() {
case "maxLength": case "maxLength":
value = limitNumber(value, 100, 10000); value = limitNumber(value, 100, 10000);
break; break;
case "newlineLength":
value = limitNumber(value, 1, 1000);
break;
default: default:
} }
updateSetting({ updateSetting({
@@ -45,10 +49,13 @@ export default function Settings() {
minLength, minLength,
maxLength, maxLength,
openaiUrl, openaiUrl,
deeplUrl = "",
deeplKey = "",
openaiKey, openaiKey,
openaiModel, openaiModel,
openaiPrompt, openaiPrompt,
clearCache, clearCache,
newlineLength = TRANS_NEWLINE_LENGTH,
} = setting; } = setting;
return ( return (
@@ -106,6 +113,15 @@ export default function Settings() {
onChange={handleChange} onChange={handleChange}
/> />
<TextField
size="small"
label={i18n("num_of_newline_characters")}
type="number"
name="newlineLength"
value={newlineLength}
onChange={handleChange}
/>
<FormControl size="small"> <FormControl size="small">
<InputLabel>{i18n("clear_cache")}</InputLabel> <InputLabel>{i18n("clear_cache")}</InputLabel>
<Select <Select
@@ -125,6 +141,25 @@ export default function Settings() {
name="googleUrl" name="googleUrl"
value={googleUrl} value={googleUrl}
onChange={handleChange} onChange={handleChange}
helperText={
<Link href={URL_KISS_PROXY}>{i18n("about_api_proxy")}</Link>
}
/>
<TextField
size="small"
label={i18n("deepl_api")}
name="deeplUrl"
value={deeplUrl}
onChange={handleChange}
/>
<TextField
size="small"
label={i18n("deepl_key")}
name="deeplKey"
value={deeplKey}
onChange={handleChange}
/> />
<TextField <TextField
@@ -133,6 +168,9 @@ export default function Settings() {
name="openaiUrl" name="openaiUrl"
value={openaiUrl} value={openaiUrl}
onChange={handleChange} onChange={handleChange}
helperText={
<Link href={URL_KISS_PROXY}>{i18n("about_api_proxy")}</Link>
}
/> />
<TextField <TextField

View File

@@ -12,12 +12,14 @@ import Button from "@mui/material/Button";
import { useAlert } from "../../hooks/Alert"; import { useAlert } from "../../hooks/Alert";
import SyncIcon from "@mui/icons-material/Sync"; import SyncIcon from "@mui/icons-material/Sync";
import CircularProgress from "@mui/material/CircularProgress"; import CircularProgress from "@mui/material/CircularProgress";
import { useSetting } from "../../hooks/Setting";
export default function SyncSetting() { export default function SyncSetting() {
const i18n = useI18n(); const i18n = useI18n();
const { sync, updateSync } = useSync(); const { sync, updateSync } = useSync();
const alert = useAlert(); const alert = useAlert();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const { reloadSetting } = useSetting();
const handleChange = async (e) => { const handleChange = async (e) => {
e.preventDefault(); e.preventDefault();
@@ -32,6 +34,7 @@ export default function SyncSetting() {
try { try {
setLoading(true); setLoading(true);
await syncSettingAndRules(); await syncSettingAndRules();
await reloadSetting();
alert.success(i18n("data_sync_success")); alert.success(i18n("data_sync_success"));
} catch (err) { } catch (err) {
console.log("[sync all]", err); console.log("[sync all]", err);

View File

@@ -15,9 +15,11 @@ import { AlertProvider } from "../../hooks/Alert";
import Link from "@mui/material/Link"; import Link from "@mui/material/Link";
import Divider from "@mui/material/Divider"; import Divider from "@mui/material/Divider";
import Stack from "@mui/material/Stack"; import Stack from "@mui/material/Stack";
import { adaptScript } from "../../libs/gm";
import Alert from "@mui/material/Alert";
export default function Options() { export default function Options() {
const [error, setError] = useState(false); const [error, setError] = useState("");
const [ready, setReady] = useState(false); const [ready, setReady] = useState(false);
useEffect(() => { useEffect(() => {
@@ -26,7 +28,22 @@ export default function Options() {
// 等待GM注入 // 等待GM注入
let i = 0; let i = 0;
for (;;) { for (;;) {
if (window.APP_NAME === process.env.REACT_APP_NAME) { if (window?.APP_INFO?.name === process.env.REACT_APP_NAME) {
const { version, eventName } = window.APP_INFO;
// 检查版本是否一致
if (version !== process.env.REACT_APP_VERSION) {
setError(
`The version of the script(v${version}) and this page(v${process.env.REACT_APP_VERSION}) are inconsistent.`
);
break;
}
if (eventName) {
// 注入GM接口
adaptScript(eventName);
}
// 同步数据 // 同步数据
await trySyncSettingAndRules(); await trySyncSettingAndRules();
setReady(true); setReady(true);
@@ -34,7 +51,7 @@ export default function Options() {
} }
if (++i > 8) { if (++i > 8) {
setError(true); setError("Time out.");
break; break;
} }
@@ -51,6 +68,7 @@ export default function Options() {
if (error) { if (error) {
return ( return (
<center> <center>
<Alert severity="error">{error}</Alert>
<Divider> <Divider>
<Link <Link
href={process.env.REACT_APP_HOMEPAGE} href={process.env.REACT_APP_HOMEPAGE}

View File

@@ -18,6 +18,7 @@ import {
OPT_LANGS_FROM, OPT_LANGS_FROM,
OPT_LANGS_TO, OPT_LANGS_TO,
OPT_STYLE_ALL, OPT_STYLE_ALL,
OPT_STYLE_USE_COLOR,
} from "../../config"; } from "../../config";
import { sendIframeMsg } from "../../libs/iframe"; import { sendIframeMsg } from "../../libs/iframe";
@@ -172,13 +173,15 @@ export default function Popup({ setShowPopup, translator: tran }) {
))} ))}
</TextField> </TextField>
<TextField {OPT_STYLE_USE_COLOR.includes(textStyle) && (
size="small" <TextField
name="bgColor" size="small"
value={bgColor} name="bgColor"
label={i18n("bg_color")} value={bgColor}
onChange={handleChange} label={i18n("bg_color")}
/> onChange={handleChange}
/>
)}
<Button variant="text" onClick={handleOpenSetting}> <Button variant="text" onClick={handleOpenSetting}>
{i18n("setting")} {i18n("setting")}

979
yarn.lock

File diff suppressed because it is too large Load Diff