Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2d7668ba7 | ||
|
|
aa830f5e20 | ||
|
|
b593fa4146 | ||
|
|
b00b906484 | ||
|
|
c1bd6a1be6 | ||
|
|
36739f04b3 | ||
|
|
23eb92853e | ||
|
|
5ab2910dc7 | ||
|
|
40d07f6764 | ||
|
|
5c8e216169 | ||
|
|
5ba061deda | ||
|
|
935c83185d | ||
|
|
6327391e65 | ||
|
|
3d656cf5b0 | ||
|
|
d570a0f1a2 | ||
|
|
503a71302c | ||
|
|
3e36ceb5b9 | ||
|
|
cde7a1d49f | ||
|
|
b14a38e4fb |
2
.env
2
.env
@@ -2,7 +2,7 @@ GENERATE_SOURCEMAP=false
|
|||||||
|
|
||||||
REACT_APP_NAME=KISS Translator
|
REACT_APP_NAME=KISS Translator
|
||||||
REACT_APP_NAME_CN=简约翻译
|
REACT_APP_NAME_CN=简约翻译
|
||||||
REACT_APP_VERSION=1.6.0
|
REACT_APP_VERSION=1.6.2
|
||||||
|
|
||||||
REACT_APP_HOMEPAGE=https://github.com/fishjar/kiss-translator
|
REACT_APP_HOMEPAGE=https://github.com/fishjar/kiss-translator
|
||||||
|
|
||||||
|
|||||||
15
README.en.md
15
README.en.md
@@ -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
|
||||||
@@ -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)
|
||||||
|
|||||||
15
README.md
15
README.md
@@ -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] 适配浏览器
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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.2",
|
||||||
"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": {
|
||||||
|
|||||||
@@ -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.2",
|
||||||
"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"
|
||||||
|
|||||||
@@ -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.2",
|
||||||
"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"
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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`,
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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 = [
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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 };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 };
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const FuzzySpan = styled.span`
|
||||||
|
filter: blur(5px);
|
||||||
|
transition: filter 0.2s ease-in-out;
|
||||||
|
&hover: {
|
||||||
|
filter: none;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const HighlightSpan = styled.span`
|
||||||
|
color: #fff;
|
||||||
|
background-color: ${(props) => props.$bgColor};
|
||||||
|
&hover: {
|
||||||
|
filter: none;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
175
src/views/Options/OwSubRule.js
Normal file
175
src/views/Options/OwSubRule.js
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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,6 +269,7 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
|
|||||||
))}
|
))}
|
||||||
</TextField>
|
</TextField>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
{OPT_STYLE_USE_COLOR.includes(textStyle) && (
|
||||||
<Grid item xs={12} sm={6} md={3} lg={2}>
|
<Grid item xs={12} sm={6} md={3} lg={2}>
|
||||||
<TextField
|
<TextField
|
||||||
size="small"
|
size="small"
|
||||||
@@ -273,9 +281,23 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
|
|||||||
onChange={handleChange}
|
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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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,6 +173,7 @@ export default function Popup({ setShowPopup, translator: tran }) {
|
|||||||
))}
|
))}
|
||||||
</TextField>
|
</TextField>
|
||||||
|
|
||||||
|
{OPT_STYLE_USE_COLOR.includes(textStyle) && (
|
||||||
<TextField
|
<TextField
|
||||||
size="small"
|
size="small"
|
||||||
name="bgColor"
|
name="bgColor"
|
||||||
@@ -179,6 +181,7 @@ export default function Popup({ setShowPopup, translator: tran }) {
|
|||||||
label={i18n("bg_color")}
|
label={i18n("bg_color")}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<Button variant="text" onClick={handleOpenSetting}>
|
<Button variant="text" onClick={handleOpenSetting}>
|
||||||
{i18n("setting")}
|
{i18n("setting")}
|
||||||
|
|||||||
Reference in New Issue
Block a user