Compare commits
65 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64d827fdcd | ||
|
|
74ad812f37 | ||
|
|
364c829119 | ||
|
|
1ac2c5b61e | ||
|
|
0766199353 | ||
|
|
878bccf151 | ||
|
|
acbd258296 | ||
|
|
54a14e6e5a | ||
|
|
298e4b52f0 | ||
|
|
bee1fbcf88 | ||
|
|
345a34287e | ||
|
|
441a2ca2da | ||
|
|
1ff1b21355 | ||
|
|
117ca4e05b | ||
|
|
07d457be4e | ||
|
|
d48296046e | ||
|
|
56350de2cf | ||
|
|
850dc0e83b | ||
|
|
35f01478b1 | ||
|
|
f9a3ec012f | ||
|
|
3b9b404482 | ||
|
|
d8b0cc4834 | ||
|
|
da13f5e218 | ||
|
|
08e14ae11c | ||
|
|
c2902dff28 | ||
|
|
c4fb39f02f | ||
|
|
b7df44c35a | ||
|
|
9a2b21eee5 | ||
|
|
bdac67df88 | ||
|
|
0b8f19bfad | ||
|
|
c7c5866131 | ||
|
|
f772fa000c | ||
|
|
93fd82fcd9 | ||
|
|
3ae10bfd04 | ||
|
|
a44747ccad | ||
|
|
87ab45f936 | ||
|
|
37b046eb46 | ||
|
|
c6f8a45027 | ||
|
|
6ec16e1f98 | ||
|
|
40adf85b20 | ||
|
|
4c78f469c1 | ||
|
|
55af58faac | ||
|
|
4200caa641 | ||
|
|
0ac06f8e3d | ||
|
|
966c78fb16 | ||
|
|
5c5a35d3bb | ||
|
|
2c24214f48 | ||
|
|
67d9e70b3c | ||
|
|
000a55f43b | ||
|
|
4096a6976c | ||
|
|
df4c4ebd50 | ||
|
|
b43bd4e0e2 | ||
|
|
2660dbf866 | ||
|
|
e0b7c60099 | ||
|
|
536b58bf67 | ||
|
|
6bb742f828 | ||
|
|
72742e5e12 | ||
|
|
3667e0a509 | ||
|
|
c2d7668ba7 | ||
|
|
aa830f5e20 | ||
|
|
b593fa4146 | ||
|
|
b00b906484 | ||
|
|
c1bd6a1be6 | ||
|
|
36739f04b3 | ||
|
|
23eb92853e |
9
.env
9
.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.1
|
REACT_APP_VERSION=1.6.9
|
||||||
|
|
||||||
REACT_APP_HOMEPAGE=https://github.com/fishjar/kiss-translator
|
REACT_APP_HOMEPAGE=https://github.com/fishjar/kiss-translator
|
||||||
|
|
||||||
@@ -13,8 +13,11 @@ REACT_APP_OPTIONSPAGE_DEV=http://localhost:3000/options.html
|
|||||||
REACT_APP_LOGOURL=https://fishjar.github.io/kiss-translator/images/logo192.png
|
REACT_APP_LOGOURL=https://fishjar.github.io/kiss-translator/images/logo192.png
|
||||||
REACT_APP_LOGOURL2=https://kiss-translator.rayjar.com/images/logo192.png
|
REACT_APP_LOGOURL2=https://kiss-translator.rayjar.com/images/logo192.png
|
||||||
|
|
||||||
REACT_APP_RULESURL=https://fishjar.github.io/kiss-translator/kiss-translator-rules.json
|
REACT_APP_RULESURL=https://fishjar.github.io/kiss-rules/kiss-rules.json
|
||||||
REACT_APP_RULESURL2=https://kiss-translator.rayjar.com/kiss-translator-rules.json
|
REACT_APP_RULESURL_ON=https://fishjar.github.io/kiss-rules/kiss-rules-on.json
|
||||||
|
REACT_APP_RULESURL_OFF=https://fishjar.github.io/kiss-rules/kiss-rules-off.json
|
||||||
|
|
||||||
|
REACT_APP_WEBFIXURL=https://fishjar.github.io/kiss-rules/kiss-webfix.json
|
||||||
|
|
||||||
REACT_APP_VERSIONFILE=https://fishjar.github.io/kiss-translator/version.txt
|
REACT_APP_VERSIONFILE=https://fishjar.github.io/kiss-translator/version.txt
|
||||||
REACT_APP_VERSIONFILE2=https://kiss-translator.rayjar.com/version.txt
|
REACT_APP_VERSIONFILE2=https://kiss-translator.rayjar.com/version.txt
|
||||||
|
|||||||
47
README.en.md
47
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,39 @@ 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
|
## Associated ProjectS
|
||||||
|
|
||||||
|
- Data synchronization service: [https://github.com/fishjar/kiss-worker](https://github.com/fishjar/kiss-worker)
|
||||||
|
- Data synchronization service available for this project.
|
||||||
|
- Can also be used to share personal private rule lists.
|
||||||
|
- Deploy by yourself, manage by yourself, data is private.
|
||||||
|
- Community subscription rules: [https://github.com/fishjar/kiss-rules](https://github.com/fishjar/kiss-rules)
|
||||||
|
- Provides the latest and most complete list of subscription rules maintained by the community.
|
||||||
|
- Help with rules-related issues.
|
||||||
|
- Web page correction script: [https://github.com/fishjar/kiss-webfixer](https://github.com/fishjar/kiss-webfixer)
|
||||||
|
- Fixed scripts for some special sites.
|
||||||
|
- So that the translation software can get better display effect.
|
||||||
|
- Translation interface agent: [https://github.com/fishjar/kiss-proxy](https://github.com/fishjar/kiss-proxy)
|
||||||
|
- If you encounter network problems when accessing a certain translation interface, this proxy service may help you.
|
||||||
|
- Deploy and manage by yourself.
|
||||||
|
- Minimalistic Dictionary Plugin: [https://github.com/fishjar/kiss-dictionary](https://github.com/fishjar/kiss-dictionary)
|
||||||
|
- A word-marking translation plug-in used with this project.
|
||||||
|
- Supports query of English words, sentences and Chinese characters.
|
||||||
|
- Supports history records and word collections.
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
### Support shortcut keys
|
||||||
|
|
||||||
|
- `Alt+Q` Toggle Translation
|
||||||
|
- `Alt+C` Toggle Styles
|
||||||
|
- `Alt+K` Open Menu
|
||||||
|
|
||||||
|
## Schedule
|
||||||
|
|
||||||
- [x] Provide trial installation package
|
- [x] Provide trial installation package
|
||||||
- [x] Adapt browser
|
- [x] Adapt browser
|
||||||
@@ -30,8 +58,8 @@ If you also like a little more simplicity, welcome to pick it up.
|
|||||||
- [x] Support translation services
|
- [x] Support translation services
|
||||||
- [x] Google
|
- [x] Google
|
||||||
- [x] Microsoft
|
- [x] Microsoft
|
||||||
|
- [x] DeepL
|
||||||
- [x] OpenAI
|
- [x] OpenAI
|
||||||
- [ ] 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 +70,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 +82,6 @@ yarn install
|
|||||||
yarn build
|
yarn build
|
||||||
```
|
```
|
||||||
|
|
||||||
### Data Sync
|
## Discussion
|
||||||
|
|
||||||
Goto: [https://github.com/fishjar/kiss-worker](https://github.com/fishjar/kiss-worker)
|
|
||||||
|
|
||||||
### Discussion
|
|
||||||
|
|
||||||
- Join [Telegram Group](https://t.me/+RRCu_4oNwrM2NmFl)
|
- Join [Telegram Group](https://t.me/+RRCu_4oNwrM2NmFl)
|
||||||
|
|||||||
47
README.md
47
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,39 @@
|
|||||||
|
|
||||||
如果你也喜欢简约一点的,欢迎自取。
|
如果你也喜欢简约一点的,欢迎自取。
|
||||||
|
|
||||||
### 特点
|
## 特点
|
||||||
|
|
||||||
- 保持简约
|
- 保持简约
|
||||||
|
|
||||||
### 进度
|
## 关联项目
|
||||||
|
|
||||||
|
- 数据同步服务: [https://github.com/fishjar/kiss-worker](https://github.com/fishjar/kiss-worker)
|
||||||
|
- 可用于本项目的数据同步服务。
|
||||||
|
- 亦可用于分享个人的私有规则列表。
|
||||||
|
- 自己部署,自己管理,数据私有。
|
||||||
|
- 社区订阅规则: [https://github.com/fishjar/kiss-rules](https://github.com/fishjar/kiss-rules)
|
||||||
|
- 提供社区维护的,最新最全的订阅规则列表。
|
||||||
|
- 求助规则相关的问题。
|
||||||
|
- 网页修正脚本: [https://github.com/fishjar/kiss-webfixer](https://github.com/fishjar/kiss-webfixer)
|
||||||
|
- 针对一些特殊网站的修正脚本。
|
||||||
|
- 以便翻译软件得到更好的展示效果。
|
||||||
|
- 翻译接口代理: [https://github.com/fishjar/kiss-proxy](https://github.com/fishjar/kiss-proxy)
|
||||||
|
- 如果访问某个翻译接口遇到网络问题,这个代理服务也许可以帮你到你。
|
||||||
|
- 自己部署,自己管理。
|
||||||
|
- 简约词典插件: [https://github.com/fishjar/kiss-dictionary](https://github.com/fishjar/kiss-dictionary)
|
||||||
|
- 搭配本项目一起使用的划词翻译插件。
|
||||||
|
- 支持英文单词、句子、汉字的查询。
|
||||||
|
- 支持历史记录、单词收藏。
|
||||||
|
|
||||||
|
## 简要说明
|
||||||
|
|
||||||
|
### 支持快捷键
|
||||||
|
|
||||||
|
- `Alt+Q` 开启翻译
|
||||||
|
- `Alt+C` 切换样式
|
||||||
|
- `Alt+K` 打开菜单
|
||||||
|
|
||||||
|
## 进度
|
||||||
|
|
||||||
- [x] 提供试用安装包
|
- [x] 提供试用安装包
|
||||||
- [x] 适配浏览器
|
- [x] 适配浏览器
|
||||||
@@ -30,8 +58,8 @@
|
|||||||
- [x] 支持翻译服务
|
- [x] 支持翻译服务
|
||||||
- [x] Google
|
- [x] Google
|
||||||
- [x] Microsoft
|
- [x] Microsoft
|
||||||
|
- [x] DeepL
|
||||||
- [x] OpenAI
|
- [x] OpenAI
|
||||||
- [ ] 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 +70,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 +82,6 @@ yarn install
|
|||||||
yarn build
|
yarn build
|
||||||
```
|
```
|
||||||
|
|
||||||
### 数据同步
|
## 交流
|
||||||
|
|
||||||
移步: [https://github.com/fishjar/kiss-worker](https://github.com/fishjar/kiss-worker)
|
|
||||||
|
|
||||||
### 交流
|
|
||||||
|
|
||||||
- 加入 [Telegram 群](https://t.me/+RRCu_4oNwrM2NmFl)
|
- 加入 [Telegram 群](https://t.me/+RRCu_4oNwrM2NmFl)
|
||||||
|
|||||||
@@ -93,6 +93,8 @@ const userscriptWebpack = (config, env) => {
|
|||||||
// @connect translate.googleapis.com
|
// @connect translate.googleapis.com
|
||||||
// @connect api-edge.cognitive.microsofttranslator.com
|
// @connect api-edge.cognitive.microsofttranslator.com
|
||||||
// @connect edge.microsoft.com
|
// @connect edge.microsoft.com
|
||||||
|
// @connect api-free.deepl.com
|
||||||
|
// @connect api.deepl.com
|
||||||
// @connect api.openai.com
|
// @connect api.openai.com
|
||||||
// @connect openai.azure.com
|
// @connect openai.azure.com
|
||||||
// @connect workers.dev
|
// @connect workers.dev
|
||||||
@@ -100,6 +102,7 @@ const userscriptWebpack = (config, env) => {
|
|||||||
// @connect githubusercontent.com
|
// @connect githubusercontent.com
|
||||||
// @connect kiss-translator.rayjar.com
|
// @connect kiss-translator.rayjar.com
|
||||||
// @connect ghproxy.com
|
// @connect ghproxy.com
|
||||||
|
// @connect localhost:3000
|
||||||
// @run-at document-end
|
// @run-at document-end
|
||||||
// ==/UserScript==
|
// ==/UserScript==
|
||||||
|
|
||||||
|
|||||||
@@ -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.1",
|
"version": "1.6.9",
|
||||||
"author": "Gabe<yugang2002@gmail.com>",
|
"author": "Gabe<yugang2002@gmail.com>",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -15,7 +15,6 @@
|
|||||||
"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": {
|
||||||
@@ -25,10 +24,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"
|
||||||
|
|||||||
@@ -64,8 +64,25 @@
|
|||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
<div id="root">
|
<div id="root">
|
||||||
|
<div id="content">
|
||||||
|
<p>You need to enable JavaScript to run <span>this app.</span></p>
|
||||||
|
The <span>embargo</span> has just lifted to confirm that AmpereOne is
|
||||||
|
coming to Google Cloud with the C3A instances.
|
||||||
|
<br />
|
||||||
|
But these upcoming instances for now are only in private preview form.
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
Needless to say I also haven't had any AmpereOne access to check out the
|
||||||
|
performance and power efficiency of these new Arm server processors from
|
||||||
|
Ampere Computing.
|
||||||
|
<br />
|
||||||
|
</div>
|
||||||
<h2>
|
<h2>
|
||||||
<p><span>React is a JavaScript library for building user interfaces.</span></p>
|
<p>
|
||||||
|
<span
|
||||||
|
>React is a JavaScript library for building user interfaces.</span
|
||||||
|
>
|
||||||
|
</p>
|
||||||
</h2>
|
</h2>
|
||||||
<div id="addtitle"></div>
|
<div id="addtitle"></div>
|
||||||
<h2>Shadow 1</h2>
|
<h2>Shadow 1</h2>
|
||||||
|
|||||||
@@ -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.1",
|
"version": "1.6.9",
|
||||||
"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.1",
|
"version": "1.6.9",
|
||||||
"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,8 +3,9 @@ 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,
|
OPT_TRANS_CUSTOMIZE,
|
||||||
OPT_LANGS_SPECIAL,
|
OPT_LANGS_SPECIAL,
|
||||||
PROMPT_PLACE_FROM,
|
PROMPT_PLACE_FROM,
|
||||||
PROMPT_PLACE_TO,
|
PROMPT_PLACE_TO,
|
||||||
@@ -32,13 +33,12 @@ export const apiSyncData = async (url, key, data, isBg = false) =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 下载订阅规则
|
* 下载数据
|
||||||
* @param {*} url
|
* @param {*} url
|
||||||
* @param {*} isBg
|
* @param {*} isBg
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const apiFetchRules = (url, isBg = false) =>
|
export const apiFetch = (url, isBg = false) => fetchPolyfill(url, { isBg });
|
||||||
fetchPolyfill(url, { isBg });
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 谷歌翻译
|
* 谷歌翻译
|
||||||
@@ -47,8 +47,13 @@ export const apiFetchRules = (url, isBg = false) =>
|
|||||||
* @param {*} from
|
* @param {*} from
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const apiGoogleTranslate = async (translator, text, to, from, setting) => {
|
const apiGoogleTranslate = async (
|
||||||
const { googleUrl } = setting;
|
translator,
|
||||||
|
text,
|
||||||
|
to,
|
||||||
|
from,
|
||||||
|
{ url, key, useCache = true }
|
||||||
|
) => {
|
||||||
const params = {
|
const params = {
|
||||||
client: "gtx",
|
client: "gtx",
|
||||||
dt: "t",
|
dt: "t",
|
||||||
@@ -58,15 +63,20 @@ const apiGoogleTranslate = async (translator, text, to, from, setting) => {
|
|||||||
tl: to,
|
tl: to,
|
||||||
q: text,
|
q: text,
|
||||||
};
|
};
|
||||||
const input = `${googleUrl}?${queryString.stringify(params)}`;
|
const input = `${url}?${queryString.stringify(params)}`;
|
||||||
return fetchPolyfill(input, {
|
const res = await fetchPolyfill(input, {
|
||||||
headers: {
|
headers: {
|
||||||
"Content-type": "application/json",
|
"Content-type": "application/json",
|
||||||
},
|
},
|
||||||
useCache: true,
|
useCache,
|
||||||
usePool: true,
|
usePool: true,
|
||||||
translator,
|
translator,
|
||||||
|
token: key,
|
||||||
});
|
});
|
||||||
|
const trText = res.sentences.map((item) => item.trans).join(" ");
|
||||||
|
const isSame = to === res.src;
|
||||||
|
|
||||||
|
return [trText, isSame];
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -76,23 +86,72 @@ const apiGoogleTranslate = async (translator, text, to, from, setting) => {
|
|||||||
* @param {*} from
|
* @param {*} from
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const apiMicrosoftTranslate = (translator, text, to, from) => {
|
const apiMicrosoftTranslate = async (
|
||||||
|
translator,
|
||||||
|
text,
|
||||||
|
to,
|
||||||
|
from,
|
||||||
|
{ url, useCache = true }
|
||||||
|
) => {
|
||||||
const params = {
|
const params = {
|
||||||
from,
|
from,
|
||||||
to,
|
to,
|
||||||
"api-version": "3.0",
|
"api-version": "3.0",
|
||||||
};
|
};
|
||||||
const input = `${URL_MICROSOFT_TRANS}?${queryString.stringify(params)}`;
|
const input = `${url}?${queryString.stringify(params)}`;
|
||||||
return fetchPolyfill(input, {
|
const res = await fetchPolyfill(input, {
|
||||||
headers: {
|
headers: {
|
||||||
"Content-type": "application/json",
|
"Content-type": "application/json",
|
||||||
},
|
},
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify([{ Text: text }]),
|
body: JSON.stringify([{ Text: text }]),
|
||||||
useCache: true,
|
useCache,
|
||||||
usePool: true,
|
usePool: true,
|
||||||
translator,
|
translator,
|
||||||
});
|
});
|
||||||
|
const trText = res[0].translations[0].text;
|
||||||
|
const isSame = to === res[0].detectedLanguage?.language;
|
||||||
|
|
||||||
|
return [trText, isSame];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DeepL翻译
|
||||||
|
* @param {*} text
|
||||||
|
* @param {*} to
|
||||||
|
* @param {*} from
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const apiDeepLTranslate = async (
|
||||||
|
translator,
|
||||||
|
text,
|
||||||
|
to,
|
||||||
|
from,
|
||||||
|
{ url, key, useCache = true }
|
||||||
|
) => {
|
||||||
|
const data = {
|
||||||
|
text: [text],
|
||||||
|
target_lang: to,
|
||||||
|
split_sentences: "0",
|
||||||
|
};
|
||||||
|
if (from) {
|
||||||
|
data.source_lang = from;
|
||||||
|
}
|
||||||
|
const res = await fetchPolyfill(url, {
|
||||||
|
headers: {
|
||||||
|
"Content-type": "application/json",
|
||||||
|
},
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
useCache,
|
||||||
|
usePool: true,
|
||||||
|
translator,
|
||||||
|
token: key,
|
||||||
|
});
|
||||||
|
const trText = res.translations.map((item) => item.text).join(" ");
|
||||||
|
const isSame = to === res.translations[0].detected_source_language;
|
||||||
|
|
||||||
|
return [trText, isSame];
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -102,18 +161,23 @@ const apiMicrosoftTranslate = (translator, text, to, from) => {
|
|||||||
* @param {*} from
|
* @param {*} from
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const apiOpenaiTranslate = async (translator, text, to, from, setting) => {
|
const apiOpenaiTranslate = async (
|
||||||
const { openaiUrl, openaiKey, openaiModel, openaiPrompt } = setting;
|
translator,
|
||||||
let prompt = openaiPrompt
|
text,
|
||||||
|
to,
|
||||||
|
from,
|
||||||
|
{ url, key, model, prompt, useCache = true }
|
||||||
|
) => {
|
||||||
|
prompt = prompt
|
||||||
.replaceAll(PROMPT_PLACE_FROM, from)
|
.replaceAll(PROMPT_PLACE_FROM, from)
|
||||||
.replaceAll(PROMPT_PLACE_TO, to);
|
.replaceAll(PROMPT_PLACE_TO, to);
|
||||||
return fetchPolyfill(openaiUrl, {
|
const res = await fetchPolyfill(url, {
|
||||||
headers: {
|
headers: {
|
||||||
"Content-type": "application/json",
|
"Content-type": "application/json",
|
||||||
},
|
},
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
model: openaiModel,
|
model: model,
|
||||||
messages: [
|
messages: [
|
||||||
{
|
{
|
||||||
role: "system",
|
role: "system",
|
||||||
@@ -127,11 +191,52 @@ const apiOpenaiTranslate = async (translator, text, to, from, setting) => {
|
|||||||
temperature: 0,
|
temperature: 0,
|
||||||
max_tokens: 256,
|
max_tokens: 256,
|
||||||
}),
|
}),
|
||||||
useCache: true,
|
useCache,
|
||||||
usePool: true,
|
usePool: true,
|
||||||
translator,
|
translator,
|
||||||
token: openaiKey,
|
token: key,
|
||||||
});
|
});
|
||||||
|
const trText = res?.choices?.[0].message.content;
|
||||||
|
const sLang = await tryDetectLang(text);
|
||||||
|
const tLang = await tryDetectLang(trText);
|
||||||
|
const isSame = text === trText || (sLang && tLang && sLang === tLang);
|
||||||
|
|
||||||
|
return [trText, isSame];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义接口 翻译
|
||||||
|
* @param {*} text
|
||||||
|
* @param {*} to
|
||||||
|
* @param {*} from
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const apiCustomTranslate = async (
|
||||||
|
translator,
|
||||||
|
text,
|
||||||
|
to,
|
||||||
|
from,
|
||||||
|
{ url, key, useCache = true }
|
||||||
|
) => {
|
||||||
|
const res = await fetchPolyfill(url, {
|
||||||
|
headers: {
|
||||||
|
"Content-type": "application/json",
|
||||||
|
},
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({
|
||||||
|
text,
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
}),
|
||||||
|
useCache,
|
||||||
|
usePool: true,
|
||||||
|
translator,
|
||||||
|
token: key,
|
||||||
|
});
|
||||||
|
const trText = res.text;
|
||||||
|
const isSame = to === res.from;
|
||||||
|
|
||||||
|
return [trText, isSame];
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -139,34 +244,29 @@ const apiOpenaiTranslate = async (translator, text, to, from, setting) => {
|
|||||||
* @param {*} param0
|
* @param {*} param0
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const apiTranslate = async ({
|
export const apiTranslate = ({
|
||||||
translator,
|
translator,
|
||||||
q,
|
text,
|
||||||
fromLang,
|
fromLang,
|
||||||
toLang,
|
toLang,
|
||||||
setting,
|
apiSetting,
|
||||||
}) => {
|
}) => {
|
||||||
let trText = "";
|
const from = OPT_LANGS_SPECIAL[translator]?.get(fromLang) ?? fromLang;
|
||||||
let isSame = false;
|
const to = OPT_LANGS_SPECIAL[translator]?.get(toLang) ?? toLang;
|
||||||
|
const callApi = (api) => api(translator, text, to, from, apiSetting);
|
||||||
|
|
||||||
let from = OPT_LANGS_SPECIAL?.[translator]?.get(fromLang) ?? fromLang;
|
switch (translator) {
|
||||||
let to = OPT_LANGS_SPECIAL?.[translator]?.get(toLang) ?? toLang;
|
case OPT_TRANS_GOOGLE:
|
||||||
|
return callApi(apiGoogleTranslate);
|
||||||
if (translator === OPT_TRANS_GOOGLE) {
|
case OPT_TRANS_MICROSOFT:
|
||||||
const res = await apiGoogleTranslate(translator, q, to, from, setting);
|
return callApi(apiMicrosoftTranslate);
|
||||||
trText = res.sentences.map((item) => item.trans).join(" ");
|
case OPT_TRANS_DEEPL:
|
||||||
isSame = to === res.src;
|
return callApi(apiDeepLTranslate);
|
||||||
} else if (translator === OPT_TRANS_MICROSOFT) {
|
case OPT_TRANS_OPENAI:
|
||||||
const res = await apiMicrosoftTranslate(translator, q, to, from);
|
return callApi(apiOpenaiTranslate);
|
||||||
trText = res[0].translations[0].text;
|
case OPT_TRANS_CUSTOMIZE:
|
||||||
isSame = to === res[0].detectedLanguage.language;
|
return callApi(apiCustomTranslate);
|
||||||
} else if (translator === OPT_TRANS_OPENAI) {
|
default:
|
||||||
const res = await apiOpenaiTranslate(translator, q, to, from, setting);
|
return ["", false];
|
||||||
trText = res?.choices?.[0].message.content;
|
|
||||||
const sLang = await tryDetectLang(q);
|
|
||||||
const tLang = await tryDetectLang(trText);
|
|
||||||
isSame = q === trText || (sLang && tLang && sLang === tLang);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return [trText, isSame];
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,99 @@ export const UI_LANGS = [
|
|||||||
["zh", "中文"],
|
["zh", "中文"],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const customApiLangs = `["en", "English - English"],
|
||||||
|
["zh-CN", "Simplified Chinese - 简体中文"],
|
||||||
|
["zh-TW", "Traditional Chinese - 繁體中文"],
|
||||||
|
["ar", "Arabic - العربية"],
|
||||||
|
["bg", "Bulgarian - Български"],
|
||||||
|
["ca", "Catalan - Català"],
|
||||||
|
["hr", "Croatian - Hrvatski"],
|
||||||
|
["cs", "Czech - Čeština"],
|
||||||
|
["da", "Danish - Dansk"],
|
||||||
|
["nl", "Dutch - Nederlands"],
|
||||||
|
["fi", "Finnish - Suomi"],
|
||||||
|
["fr", "French - Français"],
|
||||||
|
["de", "German - Deutsch"],
|
||||||
|
["el", "Greek - Ελληνικά"],
|
||||||
|
["hi", "Hindi - हिन्दी"],
|
||||||
|
["hu", "Hungarian - Magyar"],
|
||||||
|
["id", "Indonesian - Indonesia"],
|
||||||
|
["it", "Italian - Italiano"],
|
||||||
|
["ja", "Japanese - 日本語"],
|
||||||
|
["ko", "Korean - 한국어"],
|
||||||
|
["ms", "Malay - Melayu"],
|
||||||
|
["mt", "Maltese - Malti"],
|
||||||
|
["nb", "Norwegian - Norsk Bokmål"],
|
||||||
|
["pl", "Polish - Polski"],
|
||||||
|
["pt", "Portuguese - Português"],
|
||||||
|
["ro", "Romanian - Română"],
|
||||||
|
["ru", "Russian - Русский"],
|
||||||
|
["sk", "Slovak - Slovenčina"],
|
||||||
|
["sl", "Slovenian - Slovenščina"],
|
||||||
|
["es", "Spanish - Español"],
|
||||||
|
["sv", "Swedish - Svenska"],
|
||||||
|
["ta", "Tamil - தமிழ்"],
|
||||||
|
["te", "Telugu - తెలుగు"],
|
||||||
|
["th", "Thai - ไทย"],
|
||||||
|
["tr", "Turkish - Türkçe"],
|
||||||
|
["uk", "Ukrainian - Українська"],
|
||||||
|
["vi", "Vietnamese - Tiếng Việt"],
|
||||||
|
`;
|
||||||
|
|
||||||
|
const customApiHelpZH = `/// 自定义翻译源接口说明
|
||||||
|
// 请求(Request)数据将按下面规范发送
|
||||||
|
{
|
||||||
|
url: {{YOUR_URL}},
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-type": "application/json",
|
||||||
|
"Authorization" = "Bearer {{YOUR_KEY}}"
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
text, // 需要翻译的文字
|
||||||
|
from, // 源语言,可能为空,表示需要接口自动识别语言
|
||||||
|
to, // 目标语言
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回(Response)数据需符合下面的JSON规范
|
||||||
|
{
|
||||||
|
text, // 翻译后的文字
|
||||||
|
from, // 识别的源语言
|
||||||
|
to, // 目标语言(可选)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 支持的语言代码如下
|
||||||
|
${customApiLangs}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const customApiHelpEN = `/// Custom translation source interface description
|
||||||
|
// Request data will be sent according to the following specifications
|
||||||
|
{
|
||||||
|
url: {{YOUR_URL}},
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-type": "application/json",
|
||||||
|
"Authorization" = "Bearer {{YOUR_KEY}}"
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
text, // text to be translated
|
||||||
|
from, // Source language, may be empty
|
||||||
|
to, // Target language
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The returned data must conform to the following JSON specification
|
||||||
|
{
|
||||||
|
text, // translated text
|
||||||
|
from, // Recognized source language
|
||||||
|
to, // Target language (optional)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The supported language codes are as follows
|
||||||
|
${customApiLangs}
|
||||||
|
`;
|
||||||
|
|
||||||
export const I18N = {
|
export const I18N = {
|
||||||
app_name: {
|
app_name: {
|
||||||
zh: `简约翻译`,
|
zh: `简约翻译`,
|
||||||
@@ -12,6 +105,10 @@ export const I18N = {
|
|||||||
zh: `翻译`,
|
zh: `翻译`,
|
||||||
en: `Translate`,
|
en: `Translate`,
|
||||||
},
|
},
|
||||||
|
custom_api_help: {
|
||||||
|
zh: customApiHelpZH,
|
||||||
|
en: customApiHelpEN,
|
||||||
|
},
|
||||||
translate_alt: {
|
translate_alt: {
|
||||||
zh: `翻译 (Alt+Q)`,
|
zh: `翻译 (Alt+Q)`,
|
||||||
en: `Translate (Alt+Q)`,
|
en: `Translate (Alt+Q)`,
|
||||||
@@ -24,10 +121,26 @@ export const I18N = {
|
|||||||
zh: `规则设置`,
|
zh: `规则设置`,
|
||||||
en: `Rules Setting`,
|
en: `Rules Setting`,
|
||||||
},
|
},
|
||||||
|
apis_setting: {
|
||||||
|
zh: `接口设置`,
|
||||||
|
en: `Apis Setting`,
|
||||||
|
},
|
||||||
sync_setting: {
|
sync_setting: {
|
||||||
zh: `同步设置`,
|
zh: `同步设置`,
|
||||||
en: `Sync Setting`,
|
en: `Sync Setting`,
|
||||||
},
|
},
|
||||||
|
patch_setting: {
|
||||||
|
zh: `补丁设置`,
|
||||||
|
en: `Patch Setting`,
|
||||||
|
},
|
||||||
|
patch_setting_help: {
|
||||||
|
zh: `针对一些特殊网站的修正脚本,以便翻译软件得到更好的展示效果。`,
|
||||||
|
en: `Corrected scripts for some special websites so that the translation software can get better display results.`,
|
||||||
|
},
|
||||||
|
inject_webfix: {
|
||||||
|
zh: `注入修复补丁`,
|
||||||
|
en: `Inject Webfix`,
|
||||||
|
},
|
||||||
about: {
|
about: {
|
||||||
zh: `关于`,
|
zh: `关于`,
|
||||||
en: `About`,
|
en: `About`,
|
||||||
@@ -68,6 +181,30 @@ export const I18N = {
|
|||||||
zh: `翻译服务`,
|
zh: `翻译服务`,
|
||||||
en: `Translate Service`,
|
en: `Translate Service`,
|
||||||
},
|
},
|
||||||
|
mouseover_translation: {
|
||||||
|
zh: `鼠标悬停翻译`,
|
||||||
|
en: `Mouseover translation`,
|
||||||
|
},
|
||||||
|
mk_disable: {
|
||||||
|
zh: `禁用`,
|
||||||
|
en: `Disable`,
|
||||||
|
},
|
||||||
|
mk_mouseover: {
|
||||||
|
zh: `鼠标悬停`,
|
||||||
|
en: `Mouseover`,
|
||||||
|
},
|
||||||
|
mk_ctrlKey: {
|
||||||
|
zh: `Control + 鼠标悬停`,
|
||||||
|
en: `Control + Mouseover`,
|
||||||
|
},
|
||||||
|
mk_shiftKey: {
|
||||||
|
zh: `Shift + 鼠标悬停`,
|
||||||
|
en: `Shift + Mouseover`,
|
||||||
|
},
|
||||||
|
mk_altKey: {
|
||||||
|
zh: `Alt + 鼠标悬停`,
|
||||||
|
en: `Alt + Mouseover`,
|
||||||
|
},
|
||||||
from_lang: {
|
from_lang: {
|
||||||
zh: `原文语言`,
|
zh: `原文语言`,
|
||||||
en: `Source Language`,
|
en: `Source Language`,
|
||||||
@@ -134,15 +271,15 @@ export const I18N = {
|
|||||||
},
|
},
|
||||||
personal_rules: {
|
personal_rules: {
|
||||||
zh: `个人规则`,
|
zh: `个人规则`,
|
||||||
en: `Personal Rules`,
|
en: `Rules`,
|
||||||
},
|
},
|
||||||
subscribe_rules: {
|
subscribe_rules: {
|
||||||
zh: `订阅规则`,
|
zh: `订阅规则`,
|
||||||
en: `Subscribe Rules`,
|
en: `Subscribe`,
|
||||||
},
|
},
|
||||||
overwrite_subscribe_rules: {
|
overwrite_subscribe_rules: {
|
||||||
zh: `覆写订阅规则`,
|
zh: `覆写订阅规则`,
|
||||||
en: `Overwrite Subscribe Rules`,
|
en: `Overwrite`,
|
||||||
},
|
},
|
||||||
subscribe_url: {
|
subscribe_url: {
|
||||||
zh: `订阅地址`,
|
zh: `订阅地址`,
|
||||||
@@ -201,8 +338,8 @@ export const I18N = {
|
|||||||
en: `Custom Style`,
|
en: `Custom Style`,
|
||||||
},
|
},
|
||||||
diy_style_helper: {
|
diy_style_helper: {
|
||||||
zh: `遵循“styled-components”的语法`,
|
zh: `遵循“CSS”的语法`,
|
||||||
en: `Follow the syntax of "styled-components"`,
|
en: `Follow the syntax of "CSS"`,
|
||||||
},
|
},
|
||||||
setting: {
|
setting: {
|
||||||
zh: `设置`,
|
zh: `设置`,
|
||||||
@@ -260,6 +397,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`,
|
||||||
@@ -276,7 +421,7 @@ export const I18N = {
|
|||||||
zh: `OpenAI 提示词`,
|
zh: `OpenAI 提示词`,
|
||||||
en: `OpenAI Prompt`,
|
en: `OpenAI Prompt`,
|
||||||
},
|
},
|
||||||
clear_cache: {
|
if_clear_cache: {
|
||||||
zh: `是否清除缓存`,
|
zh: `是否清除缓存`,
|
||||||
en: `Whether clear cache`,
|
en: `Whether clear cache`,
|
||||||
},
|
},
|
||||||
@@ -296,17 +441,17 @@ export const I18N = {
|
|||||||
zh: `数据同步密钥`,
|
zh: `数据同步密钥`,
|
||||||
en: `Data Sync Key`,
|
en: `Data Sync Key`,
|
||||||
},
|
},
|
||||||
data_sync_test: {
|
sync_now: {
|
||||||
zh: `数据同步测试`,
|
zh: `立即同步`,
|
||||||
en: `Data Sync Test`,
|
en: `Sync Now`,
|
||||||
},
|
},
|
||||||
data_sync_success: {
|
sync_success: {
|
||||||
zh: `数据同步成功!`,
|
zh: `同步成功!`,
|
||||||
en: `Data Sync Success`,
|
en: `Sync Success`,
|
||||||
},
|
},
|
||||||
data_sync_error: {
|
sync_failed: {
|
||||||
zh: `数据同步失败!`,
|
zh: `同步失败!`,
|
||||||
en: `Data Sync Error`,
|
en: `Sync Error`,
|
||||||
},
|
},
|
||||||
error_got_some_wrong: {
|
error_got_some_wrong: {
|
||||||
zh: `抱歉,出错了!`,
|
zh: `抱歉,出错了!`,
|
||||||
@@ -316,4 +461,80 @@ export const I18N = {
|
|||||||
zh: `您的同步设置未填写,无法在线分享。`,
|
zh: `您的同步设置未填写,无法在线分享。`,
|
||||||
en: `Your sync settings are missing and cannot be shared online.`,
|
en: `Your sync settings are missing and cannot be shared online.`,
|
||||||
},
|
},
|
||||||
|
click_test: {
|
||||||
|
zh: `点击测试`,
|
||||||
|
en: `Click Test`,
|
||||||
|
},
|
||||||
|
test_success: {
|
||||||
|
zh: `测试成功`,
|
||||||
|
en: `Test success`,
|
||||||
|
},
|
||||||
|
test_failed: {
|
||||||
|
zh: `测试失败`,
|
||||||
|
en: `Test failed`,
|
||||||
|
},
|
||||||
|
clear_all_cache_now: {
|
||||||
|
zh: `立即清除全部缓存`,
|
||||||
|
en: `Clear all cache now`,
|
||||||
|
},
|
||||||
|
clear_cache: {
|
||||||
|
zh: `清除缓存`,
|
||||||
|
en: `Clear Cache`,
|
||||||
|
},
|
||||||
|
clear_success: {
|
||||||
|
zh: `清除成功`,
|
||||||
|
en: `Clear success`,
|
||||||
|
},
|
||||||
|
clear_failed: {
|
||||||
|
zh: `清除失败`,
|
||||||
|
en: `Clear failed`,
|
||||||
|
},
|
||||||
|
share: {
|
||||||
|
zh: `分享`,
|
||||||
|
en: `Share`,
|
||||||
|
},
|
||||||
|
clear_all: {
|
||||||
|
zh: `清空`,
|
||||||
|
en: `Clear All`,
|
||||||
|
},
|
||||||
|
help: {
|
||||||
|
zh: `求助`,
|
||||||
|
en: `Help`,
|
||||||
|
},
|
||||||
|
restore_default: {
|
||||||
|
zh: `恢复默认`,
|
||||||
|
en: `Restore Default`,
|
||||||
|
},
|
||||||
|
shortcuts_setting: {
|
||||||
|
zh: `快捷键设置`,
|
||||||
|
en: `Shortcuts Setting`,
|
||||||
|
},
|
||||||
|
toggle_translate_shortcut: {
|
||||||
|
zh: `"开启翻译"快捷键`,
|
||||||
|
en: `"Toggle Translate" Shortcut`,
|
||||||
|
},
|
||||||
|
toggle_style_shortcut: {
|
||||||
|
zh: `"切换样式"快捷键`,
|
||||||
|
en: `"Toggle Style" Shortcut`,
|
||||||
|
},
|
||||||
|
toggle_popup_shortcut: {
|
||||||
|
zh: `"打开弹窗"快捷键`,
|
||||||
|
en: `"Open Popup" Shortcut`,
|
||||||
|
},
|
||||||
|
open_setting_shortcut: {
|
||||||
|
zh: `"打开设置"快捷键`,
|
||||||
|
en: `"Open Setting" Shortcut`,
|
||||||
|
},
|
||||||
|
hide_fab_button: {
|
||||||
|
zh: `隐藏悬浮按钮`,
|
||||||
|
en: `"Hide Fab Button`,
|
||||||
|
},
|
||||||
|
show: {
|
||||||
|
zh: `显示`,
|
||||||
|
en: `"Show`,
|
||||||
|
},
|
||||||
|
hide: {
|
||||||
|
zh: `隐藏`,
|
||||||
|
en: `"Hide`,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ export const STOKEY_RULES = `${APP_NAME}_rules`;
|
|||||||
export const STOKEY_SYNC = `${APP_NAME}_sync`;
|
export const STOKEY_SYNC = `${APP_NAME}_sync`;
|
||||||
export const STOKEY_FAB = `${APP_NAME}_fab`;
|
export const STOKEY_FAB = `${APP_NAME}_fab`;
|
||||||
export const STOKEY_RULESCACHE_PREFIX = `${APP_NAME}_rulescache_`;
|
export const STOKEY_RULESCACHE_PREFIX = `${APP_NAME}_rulescache_`;
|
||||||
|
export const STOKEY_WEBFIXCACHE_PREFIX = `${APP_NAME}_webfixcache_`;
|
||||||
|
|
||||||
export const CMD_TOGGLE_TRANSLATE = "toggleTranslate";
|
export const CMD_TOGGLE_TRANSLATE = "toggleTranslate";
|
||||||
export const CMD_TOGGLE_STYLE = "toggleStyle";
|
export const CMD_TOGGLE_STYLE = "toggleStyle";
|
||||||
@@ -53,26 +54,29 @@ 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_KISS_PROXY = "https://github.com/fishjar/kiss-proxy";
|
||||||
|
export const URL_KISS_RULES = "https://github.com/fishjar/kiss-rules";
|
||||||
|
export const URL_KISS_RULES_NEW_ISSUE =
|
||||||
|
"https://github.com/fishjar/kiss-rules/issues/new";
|
||||||
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";
|
||||||
export const URL_MICROSOFT_TRANS =
|
|
||||||
"https://api-edge.cognitive.microsofttranslator.com/translate";
|
|
||||||
|
|
||||||
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_CUSTOMIZE = "Custom";
|
||||||
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,
|
||||||
|
OPT_TRANS_CUSTOMIZE,
|
||||||
];
|
];
|
||||||
|
|
||||||
export const OPT_LANGS_TO = [
|
export const OPT_LANGS_TO = [
|
||||||
@@ -121,9 +125,16 @@ 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]])
|
||||||
),
|
),
|
||||||
|
[OPT_TRANS_CUSTOMIZE]: new Map([["auto", ""]]),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const OPT_STYLE_NONE = "style_none"; // 无
|
export const OPT_STYLE_NONE = "style_none"; // 无
|
||||||
@@ -152,6 +163,19 @@ export const OPT_STYLE_USE_COLOR = [
|
|||||||
OPT_STYLE_HIGHLIGHT,
|
OPT_STYLE_HIGHLIGHT,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const OPT_MOUSEKEY_DISABLE = "mk_disable";
|
||||||
|
export const OPT_MOUSEKEY_MOUSEOVER = "mk_mouseover";
|
||||||
|
export const OPT_MOUSEKEY_CONTROL = "mk_ctrlKey";
|
||||||
|
export const OPT_MOUSEKEY_SHIFT = "mk_shiftKey";
|
||||||
|
export const OPT_MOUSEKEY_ALT = "mk_altKey";
|
||||||
|
export const OPT_MOUSEKEY_ALL = [
|
||||||
|
OPT_MOUSEKEY_DISABLE,
|
||||||
|
OPT_MOUSEKEY_MOUSEOVER,
|
||||||
|
OPT_MOUSEKEY_CONTROL,
|
||||||
|
OPT_MOUSEKEY_SHIFT,
|
||||||
|
OPT_MOUSEKEY_ALT,
|
||||||
|
];
|
||||||
|
|
||||||
export const DEFAULT_FETCH_LIMIT = 10; // 默认最大任务数量
|
export const DEFAULT_FETCH_LIMIT = 10; // 默认最大任务数量
|
||||||
export const DEFAULT_FETCH_INTERVAL = 100; // 默认任务间隔时间
|
export const DEFAULT_FETCH_INTERVAL = 100; // 默认任务间隔时间
|
||||||
|
|
||||||
@@ -177,14 +201,56 @@ export const GLOBLA_RULE = {
|
|||||||
export const DEFAULT_SUBRULES_LIST = [
|
export const DEFAULT_SUBRULES_LIST = [
|
||||||
{
|
{
|
||||||
url: process.env.REACT_APP_RULESURL,
|
url: process.env.REACT_APP_RULESURL,
|
||||||
|
selected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: process.env.REACT_APP_RULESURL_ON,
|
||||||
selected: true,
|
selected: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
url: process.env.REACT_APP_RULESURL2,
|
url: process.env.REACT_APP_RULESURL_OFF,
|
||||||
selected: false,
|
selected: false,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// 翻译接口
|
||||||
|
export const DEFAULT_TRANS_APIS = {
|
||||||
|
[OPT_TRANS_GOOGLE]: {
|
||||||
|
url: "https://translate.googleapis.com/translate_a/single",
|
||||||
|
key: "",
|
||||||
|
},
|
||||||
|
[OPT_TRANS_MICROSOFT]: {
|
||||||
|
url: "https://api-edge.cognitive.microsofttranslator.com/translate",
|
||||||
|
authUrl: "https://edge.microsoft.com/translate/auth",
|
||||||
|
},
|
||||||
|
[OPT_TRANS_DEEPL]: {
|
||||||
|
url: "https://api-free.deepl.com/v2/translate",
|
||||||
|
key: "",
|
||||||
|
},
|
||||||
|
[OPT_TRANS_OPENAI]: {
|
||||||
|
url: "https://api.openai.com/v1/chat/completion",
|
||||||
|
key: "",
|
||||||
|
model: "gpt-4",
|
||||||
|
prompt: `You will be provided with a sentence in ${PROMPT_PLACE_FROM}, and your task is to translate it into ${PROMPT_PLACE_TO}.`,
|
||||||
|
},
|
||||||
|
[OPT_TRANS_CUSTOMIZE]: {
|
||||||
|
url: "",
|
||||||
|
key: "",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// 默认快捷键
|
||||||
|
export const OPT_SHORTCUT_TRANSLATE = "toggleTranslate";
|
||||||
|
export const OPT_SHORTCUT_STYLE = "toggleStyle";
|
||||||
|
export const OPT_SHORTCUT_POPUP = "togglePopup";
|
||||||
|
export const OPT_SHORTCUT_SETTING = "openSetting";
|
||||||
|
export const DEFAULT_SHORTCUTS = {
|
||||||
|
[OPT_SHORTCUT_TRANSLATE]: ["Alt", "q"],
|
||||||
|
[OPT_SHORTCUT_STYLE]: ["Alt", "c"],
|
||||||
|
[OPT_SHORTCUT_POPUP]: ["Alt", "k"],
|
||||||
|
[OPT_SHORTCUT_SETTING]: ["Alt", "o"],
|
||||||
|
};
|
||||||
|
|
||||||
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 TRANS_NEWLINE_LENGTH = 40; // 换行字符数
|
||||||
@@ -199,13 +265,13 @@ export const DEFAULT_SETTING = {
|
|||||||
newlineLength: TRANS_NEWLINE_LENGTH,
|
newlineLength: TRANS_NEWLINE_LENGTH,
|
||||||
clearCache: false, // 是否在浏览器下次启动时清除缓存
|
clearCache: false, // 是否在浏览器下次启动时清除缓存
|
||||||
injectRules: true, // 是否注入订阅规则
|
injectRules: true, // 是否注入订阅规则
|
||||||
|
injectWebfix: true, // 是否注入修复补丁
|
||||||
subrulesList: DEFAULT_SUBRULES_LIST, // 订阅列表
|
subrulesList: DEFAULT_SUBRULES_LIST, // 订阅列表
|
||||||
owSubrule: DEFAULT_OW_RULE, // 覆写订阅规则
|
owSubrule: DEFAULT_OW_RULE, // 覆写订阅规则
|
||||||
googleUrl: "https://translate.googleapis.com/translate_a/single", // 谷歌翻译接口
|
transApis: DEFAULT_TRANS_APIS, // 翻译接口
|
||||||
openaiUrl: "https://api.openai.com/v1/chat/completions",
|
mouseKey: OPT_MOUSEKEY_DISABLE, // 鼠标悬停翻译
|
||||||
openaiKey: "",
|
shortcuts: DEFAULT_SHORTCUTS, // 快捷键
|
||||||
openaiModel: "gpt-4",
|
hideFab: false, // 是否隐藏按钮
|
||||||
openaiPrompt: `You will be provided with a sentence in ${PROMPT_PLACE_FROM}, and your task is to translate it into ${PROMPT_PLACE_TO}.`,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DEFAULT_RULES = [GLOBLA_RULE];
|
export const DEFAULT_RULES = [GLOBLA_RULE];
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const els = `li, p, h1, h2, h3, h4, h5, h6, dd`;
|
const els = `li, p, h1, h2, h3, h4, h5, h6, dd, blockquote`;
|
||||||
|
|
||||||
export const DEFAULT_SELECTOR = `:is(${els})`;
|
export const DEFAULT_SELECTOR = `:is(${els})`;
|
||||||
|
|
||||||
@@ -177,7 +177,9 @@ const RULES = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const BUILTIN_RULES = RULES.map((item) => ({
|
export const BUILTIN_RULES = RULES.sort((a, b) =>
|
||||||
|
a.pattern.localeCompare(b.pattern)
|
||||||
|
).map((item) => ({
|
||||||
...DEFAULT_RULE,
|
...DEFAULT_RULE,
|
||||||
...item,
|
...item,
|
||||||
transOpen: "true",
|
transOpen: "true",
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { getSettingWithDefault, getRulesWithDefault } from "./libs/storage";
|
|||||||
import { Translator } from "./libs/translator";
|
import { Translator } from "./libs/translator";
|
||||||
import { isIframe } from "./libs/iframe";
|
import { isIframe } from "./libs/iframe";
|
||||||
import { matchRule } from "./libs/rules";
|
import { matchRule } from "./libs/rules";
|
||||||
|
import { webfix } from "./libs/webfix";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 入口函数
|
* 入口函数
|
||||||
@@ -19,6 +20,7 @@ const init = async () => {
|
|||||||
const rules = await getRulesWithDefault();
|
const rules = await getRulesWithDefault();
|
||||||
const rule = await matchRule(rules, href, setting);
|
const rule = await matchRule(rules, href, setting);
|
||||||
const translator = new Translator(rule, setting);
|
const translator = new Translator(rule, setting);
|
||||||
|
webfix(href, setting);
|
||||||
|
|
||||||
// 监听消息
|
// 监听消息
|
||||||
browser?.runtime.onMessage.addListener(async ({ action, args }) => {
|
browser?.runtime.onMessage.addListener(async ({ action, args }) => {
|
||||||
@@ -45,9 +47,10 @@ const init = async () => {
|
|||||||
try {
|
try {
|
||||||
await init();
|
await init();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error("[KISS-Translator]", err);
|
||||||
const $err = document.createElement("div");
|
const $err = document.createElement("div");
|
||||||
$err.innerText = `KISS-Translator: ${err.message}`;
|
$err.innerText = `KISS-Translator: ${err.message}`;
|
||||||
$err.style.cssText = "background:red; color:#fff; z-index:10000;";
|
$err.style.cssText = "background:red; color:#fff;";
|
||||||
document.body.prepend($err);
|
document.body.prepend($err);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|||||||
24
src/hooks/Api.js
Normal file
24
src/hooks/Api.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { useCallback } from "react";
|
||||||
|
import { DEFAULT_TRANS_APIS } from "../config";
|
||||||
|
import { useSetting } from "./Setting";
|
||||||
|
|
||||||
|
export function useApi(translator) {
|
||||||
|
const { setting, updateSetting } = useSetting();
|
||||||
|
const transApis = setting?.transApis || DEFAULT_TRANS_APIS;
|
||||||
|
|
||||||
|
const updateApi = useCallback(
|
||||||
|
async (obj) => {
|
||||||
|
const api = transApis[translator] || {};
|
||||||
|
Object.assign(transApis, { [translator]: { ...api, ...obj } });
|
||||||
|
await updateSetting({ transApis });
|
||||||
|
},
|
||||||
|
[translator, transApis, updateSetting]
|
||||||
|
);
|
||||||
|
|
||||||
|
const resetApi = useCallback(async () => {
|
||||||
|
Object.assign(transApis, { [translator]: DEFAULT_TRANS_APIS[translator] });
|
||||||
|
await updateSetting({ transApis });
|
||||||
|
}, [translator, transApis, updateSetting]);
|
||||||
|
|
||||||
|
return { api: transApis[translator] || {}, updateApi, resetApi };
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import { STOKEY_RULES, DEFAULT_RULES } from "../config";
|
import { STOKEY_RULES, DEFAULT_RULES } from "../config";
|
||||||
import { useStorage } from "./Storage";
|
import { useStorage } from "./Storage";
|
||||||
import { trySyncRules } from "../libs/sync";
|
import { trySyncRules } from "../libs/sync";
|
||||||
import { useSync } from "./Sync";
|
|
||||||
import { checkRules } from "../libs/rules";
|
import { checkRules } from "../libs/rules";
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
|
|
||||||
@@ -11,19 +10,13 @@ import { useCallback } from "react";
|
|||||||
*/
|
*/
|
||||||
export function useRules() {
|
export function useRules() {
|
||||||
const { data: list, save } = useStorage(STOKEY_RULES, DEFAULT_RULES);
|
const { data: list, save } = useStorage(STOKEY_RULES, DEFAULT_RULES);
|
||||||
const {
|
|
||||||
sync: { rulesUpdateAt },
|
|
||||||
updateSync,
|
|
||||||
} = useSync();
|
|
||||||
|
|
||||||
const updateRules = useCallback(
|
const updateRules = useCallback(
|
||||||
async (rules) => {
|
async (rules) => {
|
||||||
const updateAt = rulesUpdateAt ? Date.now() : 0;
|
|
||||||
await save(rules);
|
await save(rules);
|
||||||
await updateSync({ rulesUpdateAt: updateAt });
|
|
||||||
trySyncRules();
|
trySyncRules();
|
||||||
},
|
},
|
||||||
[rulesUpdateAt, save, updateSync]
|
[save]
|
||||||
);
|
);
|
||||||
|
|
||||||
const add = useCallback(
|
const add = useCallback(
|
||||||
@@ -53,6 +46,12 @@ export function useRules() {
|
|||||||
[list, updateRules]
|
[list, updateRules]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const clear = useCallback(async () => {
|
||||||
|
let rules = [...list];
|
||||||
|
rules = rules.filter((item) => item.pattern === "*");
|
||||||
|
await updateRules(rules);
|
||||||
|
}, [list, updateRules]);
|
||||||
|
|
||||||
const put = useCallback(
|
const put = useCallback(
|
||||||
async (pattern, obj) => {
|
async (pattern, obj) => {
|
||||||
const rules = [...list];
|
const rules = [...list];
|
||||||
@@ -85,5 +84,5 @@ export function useRules() {
|
|||||||
[list, updateRules]
|
[list, updateRules]
|
||||||
);
|
);
|
||||||
|
|
||||||
return { list, add, del, put, merge };
|
return { list, add, del, clear, put, merge };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,20 @@
|
|||||||
import { STOKEY_SETTING, DEFAULT_SETTING } from "../config";
|
import { STOKEY_SETTING, DEFAULT_SETTING } from "../config";
|
||||||
import { useStorage } from "./Storage";
|
import { useStorage } from "./Storage";
|
||||||
import { useSync } from "./Sync";
|
|
||||||
import { trySyncSetting } from "../libs/sync";
|
import { trySyncSetting } from "../libs/sync";
|
||||||
import { createContext, useCallback, useContext, useMemo } from "react";
|
import { createContext, useCallback, useContext, useMemo } from "react";
|
||||||
import { debounce } from "../libs/utils";
|
import { debounce } from "../libs/utils";
|
||||||
|
|
||||||
const SettingContext = createContext({
|
const SettingContext = createContext({
|
||||||
setting: null,
|
setting: {},
|
||||||
updateSetting: async () => {},
|
updateSetting: async () => {},
|
||||||
reloadSetting: async () => {},
|
reloadSetting: async () => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
export function SettingProvider({ children }) {
|
export function SettingProvider({ children }) {
|
||||||
const { data, update, reload } = useStorage(STOKEY_SETTING, DEFAULT_SETTING);
|
const { data, update, reload, loading } = useStorage(
|
||||||
const {
|
STOKEY_SETTING,
|
||||||
sync: { settingUpdateAt },
|
DEFAULT_SETTING
|
||||||
updateSync,
|
);
|
||||||
} = useSync();
|
|
||||||
|
|
||||||
const syncSetting = useMemo(
|
const syncSetting = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@@ -28,14 +26,16 @@ export function SettingProvider({ children }) {
|
|||||||
|
|
||||||
const updateSetting = useCallback(
|
const updateSetting = useCallback(
|
||||||
async (obj) => {
|
async (obj) => {
|
||||||
const updateAt = settingUpdateAt ? Date.now() : 0;
|
|
||||||
await update(obj);
|
await update(obj);
|
||||||
await updateSync({ settingUpdateAt: updateAt });
|
|
||||||
syncSetting();
|
syncSetting();
|
||||||
},
|
},
|
||||||
[settingUpdateAt, update, updateSync, syncSetting]
|
[update, syncSetting]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingContext.Provider
|
<SettingContext.Provider
|
||||||
value={{
|
value={{
|
||||||
|
|||||||
19
src/hooks/Shortcut.js
Normal file
19
src/hooks/Shortcut.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { useCallback } from "react";
|
||||||
|
import { DEFAULT_SHORTCUTS } from "../config";
|
||||||
|
import { useSetting } from "./Setting";
|
||||||
|
|
||||||
|
export function useShortcut(action) {
|
||||||
|
const { setting, updateSetting } = useSetting();
|
||||||
|
const shortcuts = setting?.shortcuts || DEFAULT_SHORTCUTS;
|
||||||
|
const shortcut = shortcuts[action] || [];
|
||||||
|
|
||||||
|
const setShortcut = useCallback(
|
||||||
|
async (val) => {
|
||||||
|
Object.assign(shortcuts, { [action]: val });
|
||||||
|
await updateSetting({ shortcuts });
|
||||||
|
},
|
||||||
|
[action, shortcuts, updateSetting]
|
||||||
|
);
|
||||||
|
|
||||||
|
return { shortcut, setShortcut };
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import { useCallback, useEffect, useState } from "react";
|
|||||||
import { storage } from "../libs/storage";
|
import { storage } from "../libs/storage";
|
||||||
|
|
||||||
export function useStorage(key, defaultVal = null) {
|
export function useStorage(key, defaultVal = null) {
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
const [data, setData] = useState(defaultVal);
|
const [data, setData] = useState(defaultVal);
|
||||||
|
|
||||||
const save = useCallback(
|
const save = useCallback(
|
||||||
@@ -36,9 +37,16 @@ export function useStorage(key, defaultVal = null) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
await reload();
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
await reload();
|
||||||
|
} catch (err) {
|
||||||
|
//
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
})();
|
})();
|
||||||
}, [reload]);
|
}, [reload]);
|
||||||
|
|
||||||
return { data, save, update, remove, reload };
|
return { data, save, update, remove, reload, loading };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,10 +32,23 @@ export function useSubRules() {
|
|||||||
[list, updateSetting]
|
[list, updateSetting]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const updateSub = useCallback(
|
||||||
|
async (url, obj) => {
|
||||||
|
const subrulesList = [...list];
|
||||||
|
subrulesList.forEach((item) => {
|
||||||
|
if (item.url === url) {
|
||||||
|
Object.assign(item, obj);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await updateSetting({ subrulesList });
|
||||||
|
},
|
||||||
|
[list, updateSetting]
|
||||||
|
);
|
||||||
|
|
||||||
const addSub = useCallback(
|
const addSub = useCallback(
|
||||||
async (url) => {
|
async (url) => {
|
||||||
const subrulesList = [...list];
|
const subrulesList = [...list];
|
||||||
subrulesList.push({ url, selected: false });
|
subrulesList.push({ url, selected: false, syncAt: Date.now() });
|
||||||
await updateSetting({ subrulesList });
|
await updateSetting({ subrulesList });
|
||||||
},
|
},
|
||||||
[list, updateSetting]
|
[list, updateSetting]
|
||||||
@@ -70,6 +83,7 @@ export function useSubRules() {
|
|||||||
return {
|
return {
|
||||||
subList: list,
|
subList: list,
|
||||||
selectSub,
|
selectSub,
|
||||||
|
updateSub,
|
||||||
addSub,
|
addSub,
|
||||||
delSub,
|
delSub,
|
||||||
selectedSub,
|
selectedSub,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useEffect } from "react";
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { tryDetectLang } from "../libs";
|
import { tryDetectLang } from "../libs";
|
||||||
import { apiTranslate } from "../apis";
|
import { apiTranslate } from "../apis";
|
||||||
|
import { DEFAULT_TRANS_APIS } from "../config";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 翻译hook
|
* 翻译hook
|
||||||
@@ -28,10 +29,10 @@ export function useTranslate(q, rule, setting) {
|
|||||||
} else {
|
} else {
|
||||||
const [trText, isSame] = await apiTranslate({
|
const [trText, isSame] = await apiTranslate({
|
||||||
translator,
|
translator,
|
||||||
q,
|
text: q,
|
||||||
fromLang,
|
fromLang,
|
||||||
toLang,
|
toLang,
|
||||||
setting,
|
apiSetting: (setting.transApis || DEFAULT_TRANS_APIS)[translator],
|
||||||
});
|
});
|
||||||
setText(trText);
|
setText(trText);
|
||||||
setSamelang(isSame);
|
setSamelang(isSame);
|
||||||
|
|||||||
@@ -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,
|
||||||
@@ -66,11 +67,15 @@ const newCacheReq = async (request) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const fetchApi = async ({ input, init = {}, translator, token }) => {
|
const fetchApi = async ({ input, init = {}, translator, token }) => {
|
||||||
if (translator === OPT_TRANS_MICROSOFT) {
|
if (token) {
|
||||||
init.headers["Authorization"] = `Bearer ${token}`;
|
if (translator === OPT_TRANS_DEEPL) {
|
||||||
} else if (translator === OPT_TRANS_OPENAI) {
|
init.headers["Authorization"] = `DeepL-Auth-Key ${token}`; // DeepL
|
||||||
init.headers["Authorization"] = `Bearer ${token}`; // // OpenAI
|
} else if (translator === OPT_TRANS_OPENAI) {
|
||||||
init.headers["api-key"] = token; // Azure OpenAI
|
init.headers["Authorization"] = `Bearer ${token}`; // OpenAI
|
||||||
|
init.headers["api-key"] = token; // Azure OpenAI
|
||||||
|
} else {
|
||||||
|
init.headers["Authorization"] = `Bearer ${token}`; // Microsoft & others
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isGm) {
|
if (isGm) {
|
||||||
@@ -80,7 +85,9 @@ const fetchApi = async ({ input, init = {}, translator, token }) => {
|
|||||||
} else {
|
} else {
|
||||||
info = GM.info;
|
info = GM.info;
|
||||||
}
|
}
|
||||||
const connects = info?.script?.connects || [];
|
// Tampermonkey --> .connects
|
||||||
|
// Violentmonkey --> .connect
|
||||||
|
const connects = info?.script?.connects || info?.script?.connect || [];
|
||||||
const url = new URL(input);
|
const url = new URL(input);
|
||||||
const isSafe = connects.find((item) => url.hostname.endsWith(item));
|
const isSafe = connects.find((item) => url.hostname.endsWith(item));
|
||||||
if (isSafe) {
|
if (isSafe) {
|
||||||
@@ -170,6 +177,10 @@ export const fetchData = async (
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const fetchPolyfill = async (input, { isBg = false, ...opts } = {}) => {
|
export const fetchPolyfill = async (input, { isBg = false, ...opts } = {}) => {
|
||||||
|
if (!input.trim()) {
|
||||||
|
throw new Error("URL is empty");
|
||||||
|
}
|
||||||
|
|
||||||
// 插件
|
// 插件
|
||||||
if (isExt && !isBg) {
|
if (isExt && !isBg) {
|
||||||
const res = await sendBgMsg(MSG_FETCH, { input, opts });
|
const res = await sendBgMsg(MSG_FETCH, { input, opts });
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -60,32 +60,25 @@ export const matchRule = async (
|
|||||||
const rule = rules.find((r) =>
|
const rule = rules.find((r) =>
|
||||||
r.pattern.split(",").some((p) => isMatch(href, p.trim()))
|
r.pattern.split(",").some((p) => isMatch(href, p.trim()))
|
||||||
);
|
);
|
||||||
|
const globalRule = rules.find((r) => r.pattern === GLOBAL_KEY) || GLOBLA_RULE;
|
||||||
const globalRule =
|
|
||||||
rules.find((r) =>
|
|
||||||
r.pattern.split(",").some((p) => p.trim() === GLOBAL_KEY)
|
|
||||||
) || GLOBLA_RULE;
|
|
||||||
|
|
||||||
if (!rule) {
|
if (!rule) {
|
||||||
return globalRule;
|
return globalRule;
|
||||||
}
|
}
|
||||||
|
|
||||||
rule.selector =
|
rule.selector = rule.selector?.trim() || globalRule.selector;
|
||||||
rule?.selector?.trim() ||
|
if (rule.textStyle === GLOBAL_KEY) {
|
||||||
globalRule?.selector?.trim() ||
|
rule.textStyle = globalRule.textStyle;
|
||||||
GLOBLA_RULE.selector;
|
rule.bgColor = globalRule.bgColor;
|
||||||
|
rule.textDiyStyle = globalRule.textDiyStyle;
|
||||||
rule.bgColor = rule?.bgColor?.trim() || globalRule?.bgColor?.trim();
|
} else {
|
||||||
rule.textDiyStyle =
|
rule.bgColor = rule.bgColor?.trim() || globalRule.bgColor;
|
||||||
rule?.textDiyStyle?.trim() || globalRule?.textDiyStyle?.trim();
|
rule.textDiyStyle = rule.textDiyStyle?.trim() || globalRule.textDiyStyle;
|
||||||
|
}
|
||||||
["translator", "fromLang", "toLang", "textStyle", "transOpen"].forEach(
|
["translator", "fromLang", "toLang", "transOpen"].forEach((key) => {
|
||||||
(key) => {
|
if (rule[key] === GLOBAL_KEY) {
|
||||||
if (rule[key] === GLOBAL_KEY) {
|
rule[key] = globalRule[key];
|
||||||
rule[key] = globalRule[key];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
return rule;
|
return rule;
|
||||||
};
|
};
|
||||||
|
|||||||
67
src/libs/shortcut.js
Normal file
67
src/libs/shortcut.js
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import { isSameSet } from "./utils";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 键盘快捷键监听
|
||||||
|
* @param {*} fn
|
||||||
|
* @param {*} target
|
||||||
|
* @param {*} timeout
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const shortcutListener = (fn, target = document, timeout = 3000) => {
|
||||||
|
const allkeys = new Set();
|
||||||
|
const curkeys = new Set();
|
||||||
|
let timer = null;
|
||||||
|
|
||||||
|
const handleKeydown = (e) => {
|
||||||
|
timer && clearTimeout(timer);
|
||||||
|
timer = setTimeout(() => {
|
||||||
|
allkeys.clear();
|
||||||
|
curkeys.clear();
|
||||||
|
clearTimeout(timer);
|
||||||
|
timer = null;
|
||||||
|
}, timeout);
|
||||||
|
|
||||||
|
if (e.code) {
|
||||||
|
allkeys.add(e.key);
|
||||||
|
curkeys.add(e.key);
|
||||||
|
fn([...curkeys], [...allkeys]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKeyup = (e) => {
|
||||||
|
curkeys.delete(e.key);
|
||||||
|
if (curkeys.size === 0) {
|
||||||
|
fn([...curkeys], [...allkeys]);
|
||||||
|
allkeys.clear();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
target.addEventListener("keydown", handleKeydown);
|
||||||
|
target.addEventListener("keyup", handleKeyup);
|
||||||
|
return () => {
|
||||||
|
if (timer) {
|
||||||
|
clearTimeout(timer);
|
||||||
|
timer = null;
|
||||||
|
}
|
||||||
|
target.removeEventListener("keydown", handleKeydown);
|
||||||
|
target.removeEventListener("keyup", handleKeyup);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册键盘快捷键
|
||||||
|
* @param {*} targetKeys
|
||||||
|
* @param {*} fn
|
||||||
|
* @param {*} target
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const shortcutRegister = (targetKeys = [], fn, target = document) => {
|
||||||
|
return shortcutListener((curkeys) => {
|
||||||
|
if (
|
||||||
|
targetKeys.length > 0 &&
|
||||||
|
isSameSet(new Set(targetKeys), new Set(curkeys))
|
||||||
|
) {
|
||||||
|
fn();
|
||||||
|
}
|
||||||
|
}, target);
|
||||||
|
};
|
||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
STOKEY_SYNC,
|
STOKEY_SYNC,
|
||||||
STOKEY_MSAUTH,
|
STOKEY_MSAUTH,
|
||||||
STOKEY_RULESCACHE_PREFIX,
|
STOKEY_RULESCACHE_PREFIX,
|
||||||
|
STOKEY_WEBFIXCACHE_PREFIX,
|
||||||
DEFAULT_SETTING,
|
DEFAULT_SETTING,
|
||||||
DEFAULT_RULES,
|
DEFAULT_RULES,
|
||||||
DEFAULT_SYNC,
|
DEFAULT_SYNC,
|
||||||
@@ -82,10 +83,8 @@ export const storage = {
|
|||||||
* 设置信息
|
* 设置信息
|
||||||
*/
|
*/
|
||||||
export const getSetting = () => getObj(STOKEY_SETTING);
|
export const getSetting = () => getObj(STOKEY_SETTING);
|
||||||
export const getSettingWithDefault = async () => ({
|
export const getSettingWithDefault = async () =>
|
||||||
...DEFAULT_SETTING,
|
(await getSetting()) || DEFAULT_SETTING;
|
||||||
...((await getSetting()) || {}),
|
|
||||||
});
|
|
||||||
export const setSetting = (val) => setObj(STOKEY_SETTING, val);
|
export const setSetting = (val) => setObj(STOKEY_SETTING, val);
|
||||||
export const updateSetting = (obj) => putObj(STOKEY_SETTING, obj);
|
export const updateSetting = (obj) => putObj(STOKEY_SETTING, obj);
|
||||||
|
|
||||||
@@ -106,6 +105,14 @@ export const delSubRules = (url) => del(STOKEY_RULESCACHE_PREFIX + url);
|
|||||||
export const setSubRules = (url, val) =>
|
export const setSubRules = (url, val) =>
|
||||||
setObj(STOKEY_RULESCACHE_PREFIX + url, val);
|
setObj(STOKEY_RULESCACHE_PREFIX + url, val);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修复站点
|
||||||
|
*/
|
||||||
|
export const getWebfix = (url) => getObj(STOKEY_WEBFIXCACHE_PREFIX + url);
|
||||||
|
export const getWebfixWithDefault = async () => (await getWebfix()) || [];
|
||||||
|
export const setWebfix = (url, val) =>
|
||||||
|
setObj(STOKEY_WEBFIXCACHE_PREFIX + url, val);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* fab位置
|
* fab位置
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -4,9 +4,11 @@ import {
|
|||||||
updateSync,
|
updateSync,
|
||||||
setSubRules,
|
setSubRules,
|
||||||
getSubRules,
|
getSubRules,
|
||||||
|
updateSetting,
|
||||||
} from "./storage";
|
} from "./storage";
|
||||||
import { apiFetchRules } from "../apis";
|
import { apiFetch } from "../apis";
|
||||||
import { checkRules } from "./rules";
|
import { checkRules } from "./rules";
|
||||||
|
import { isAllchar } from "./utils";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 同步订阅规则
|
* 同步订阅规则
|
||||||
@@ -14,9 +16,9 @@ import { checkRules } from "./rules";
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const syncSubRules = async (url, isBg = false) => {
|
export const syncSubRules = async (url, isBg = false) => {
|
||||||
const res = await apiFetchRules(url, isBg);
|
const res = await apiFetch(url, isBg);
|
||||||
const rules = checkRules(res).filter(
|
const rules = checkRules(res).filter(
|
||||||
(rule) => rule.pattern.replaceAll(GLOBAL_KEY, "") !== ""
|
({ pattern }) => !isAllchar(pattern, GLOBAL_KEY)
|
||||||
);
|
);
|
||||||
if (rules.length > 0) {
|
if (rules.length > 0) {
|
||||||
await setSubRules(url, rules);
|
await setSubRules(url, rules);
|
||||||
@@ -53,6 +55,10 @@ export const trySyncAllSubRules = async ({ subrulesList }, isBg = false) => {
|
|||||||
await syncAllSubRules(subrulesList, isBg);
|
await syncAllSubRules(subrulesList, isBg);
|
||||||
await updateSync({ subRulesSyncAt: now });
|
await updateSync({ subRulesSyncAt: now });
|
||||||
}
|
}
|
||||||
|
subrulesList.forEach((item) => {
|
||||||
|
item.syncAt = now;
|
||||||
|
});
|
||||||
|
await updateSetting({ subrulesList });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("[try sync all subrules]", err);
|
console.log("[try sync all subrules]", err);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import { sha256 } from "./utils";
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const syncSetting = async (isBg = false) => {
|
const syncSetting = async (isBg = false) => {
|
||||||
const { syncUrl, syncKey, settingUpdateAt } = await getSyncWithDefault();
|
const { syncUrl, syncKey, settingUpdateAt = 0 } = await getSyncWithDefault();
|
||||||
if (!syncUrl || !syncKey) {
|
if (!syncUrl || !syncKey) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -37,16 +37,15 @@ const syncSetting = async (isBg = false) => {
|
|||||||
isBg
|
isBg
|
||||||
);
|
);
|
||||||
|
|
||||||
if (res && res.updateAt > settingUpdateAt) {
|
if (res.updateAt > settingUpdateAt) {
|
||||||
await updateSync({
|
|
||||||
settingUpdateAt: res.updateAt,
|
|
||||||
settingSyncAt: res.updateAt,
|
|
||||||
});
|
|
||||||
await setSetting(res.value);
|
await setSetting(res.value);
|
||||||
return res.value;
|
|
||||||
} else {
|
|
||||||
await updateSync({ settingSyncAt: res.updateAt });
|
|
||||||
}
|
}
|
||||||
|
await updateSync({
|
||||||
|
settingUpdateAt: res.updateAt,
|
||||||
|
settingSyncAt: Date.now(),
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.value;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const trySyncSetting = async (isBg = false) => {
|
export const trySyncSetting = async (isBg = false) => {
|
||||||
@@ -79,16 +78,15 @@ const syncRules = async (isBg = false) => {
|
|||||||
isBg
|
isBg
|
||||||
);
|
);
|
||||||
|
|
||||||
if (res && res.updateAt > rulesUpdateAt) {
|
if (res.updateAt > rulesUpdateAt) {
|
||||||
await updateSync({
|
|
||||||
rulesUpdateAt: res.updateAt,
|
|
||||||
rulesSyncAt: res.updateAt,
|
|
||||||
});
|
|
||||||
await setRules(res.value);
|
await setRules(res.value);
|
||||||
return res.value;
|
|
||||||
} else {
|
|
||||||
await updateSync({ rulesSyncAt: res.updateAt });
|
|
||||||
}
|
}
|
||||||
|
await updateSync({
|
||||||
|
rulesUpdateAt: res.updateAt,
|
||||||
|
rulesSyncAt: Date.now(),
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.value;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const trySyncRules = async (isBg = false) => {
|
export const trySyncRules = async (isBg = false) => {
|
||||||
|
|||||||
@@ -3,15 +3,16 @@ 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,
|
||||||
SHADOW_KEY,
|
SHADOW_KEY,
|
||||||
|
OPT_MOUSEKEY_DISABLE,
|
||||||
|
OPT_MOUSEKEY_MOUSEOVER,
|
||||||
} 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 +38,7 @@ export class Translator {
|
|||||||
"script",
|
"script",
|
||||||
"iframe",
|
"iframe",
|
||||||
];
|
];
|
||||||
|
_eventName = genEventName();
|
||||||
|
|
||||||
// 显示
|
// 显示
|
||||||
_interseObserver = new IntersectionObserver(
|
_interseObserver = new IntersectionObserver(
|
||||||
@@ -105,6 +107,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 +121,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,
|
||||||
@@ -206,6 +213,10 @@ export class Translator {
|
|||||||
};
|
};
|
||||||
|
|
||||||
_register = () => {
|
_register = () => {
|
||||||
|
if (this._rule.fromLang === this._rule.toLang) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 搜索节点
|
// 搜索节点
|
||||||
this._queryNodes();
|
this._queryNodes();
|
||||||
|
|
||||||
@@ -219,20 +230,47 @@ export class Translator {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this._tranNodes.forEach((_, node) => {
|
this._tranNodes.forEach((_, node) => {
|
||||||
// 监听节点显示
|
if (
|
||||||
this._interseObserver.observe(node);
|
!this._setting.mouseKey ||
|
||||||
|
this._setting.mouseKey === OPT_MOUSEKEY_DISABLE
|
||||||
|
) {
|
||||||
|
// 监听节点显示
|
||||||
|
this._interseObserver.observe(node);
|
||||||
|
} else {
|
||||||
|
// 监听鼠标悬停
|
||||||
|
node.addEventListener("mouseover", this._handleMouseover);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_handleMouseover = (e) => {
|
||||||
|
const key = this._setting.mouseKey.slice(3);
|
||||||
|
if (this._setting.mouseKey === OPT_MOUSEKEY_MOUSEOVER || e[key]) {
|
||||||
|
e.target.removeEventListener("mouseover", this._handleMouseover);
|
||||||
|
this._render(e.target);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
_unRegister = () => {
|
_unRegister = () => {
|
||||||
// 解除节点变化监听
|
// 解除节点变化监听
|
||||||
this._mutaObserver.disconnect();
|
this._mutaObserver.disconnect();
|
||||||
|
|
||||||
// 解除节点显示监听
|
// 解除节点显示监听
|
||||||
this._interseObserver.disconnect();
|
// this._interseObserver.disconnect();
|
||||||
|
|
||||||
// 移除已插入元素
|
|
||||||
this._tranNodes.forEach((_, node) => {
|
this._tranNodes.forEach((_, node) => {
|
||||||
|
if (
|
||||||
|
!this._setting.mouseKey ||
|
||||||
|
this._setting.mouseKey === OPT_MOUSEKEY_DISABLE
|
||||||
|
) {
|
||||||
|
// 解除节点显示监听
|
||||||
|
this._interseObserver.unobserve(node);
|
||||||
|
} else {
|
||||||
|
// 移除鼠标悬停监听
|
||||||
|
node.removeEventListener("mouseover", this._handleMouseover);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除已插入元素
|
||||||
node.querySelector(APP_LCNAME)?.remove();
|
node.querySelector(APP_LCNAME)?.remove();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -48,15 +48,61 @@ export const sleep = (delay) =>
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const debounce = (func, delay = 200) => {
|
export const debounce = (func, delay = 200) => {
|
||||||
let timer;
|
let timer = null;
|
||||||
return (...args) => {
|
return (...args) => {
|
||||||
timer && clearTimeout(timer);
|
timer && clearTimeout(timer);
|
||||||
timer = setTimeout(() => {
|
timer = setTimeout(() => {
|
||||||
func(...args);
|
func(...args);
|
||||||
|
clearTimeout(timer);
|
||||||
|
timer = null;
|
||||||
}, delay);
|
}, delay);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节流函数
|
||||||
|
* @param {*} func
|
||||||
|
* @param {*} delay
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const throttle = (func, delay = 200) => {
|
||||||
|
let timer = null;
|
||||||
|
let cache = null;
|
||||||
|
return (...args) => {
|
||||||
|
if (!timer) {
|
||||||
|
func(...args);
|
||||||
|
cache = null;
|
||||||
|
timer = setTimeout(() => {
|
||||||
|
if (cache) {
|
||||||
|
func(...cache);
|
||||||
|
cache = null;
|
||||||
|
}
|
||||||
|
clearTimeout(timer);
|
||||||
|
timer = null;
|
||||||
|
}, delay);
|
||||||
|
} else {
|
||||||
|
cache = args;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断字符串全是某个字符
|
||||||
|
* @param {*} s
|
||||||
|
* @param {*} c
|
||||||
|
* @param {*} i
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const isAllchar = (s, c, i = 0) => {
|
||||||
|
while (i < s.length) {
|
||||||
|
if (s[i] !== c) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 字符串通配符(*)匹配
|
* 字符串通配符(*)匹配
|
||||||
* @param {*} s
|
* @param {*} s
|
||||||
@@ -68,7 +114,7 @@ export const isMatch = (s, p) => {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
p = `*${p}*`;
|
p = "*" + p + "*";
|
||||||
|
|
||||||
let [sIndex, pIndex] = [0, 0];
|
let [sIndex, pIndex] = [0, 0];
|
||||||
let [sRecord, pRecord] = [-1, -1];
|
let [sRecord, pRecord] = [-1, -1];
|
||||||
@@ -91,7 +137,7 @@ export const isMatch = (s, p) => {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return p.slice(pIndex).replaceAll("*", "") === "";
|
return isAllchar(p, "*", pIndex);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -116,3 +162,20 @@ 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);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断两个 Set 是否相同
|
||||||
|
* @param {*} a
|
||||||
|
* @param {*} b
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const isSameSet = (a, b) => {
|
||||||
|
const s = new Set([...a, ...b]);
|
||||||
|
return s.size === a.size && s.size === b.size;
|
||||||
|
};
|
||||||
|
|||||||
176
src/libs/webfix.js
Normal file
176
src/libs/webfix.js
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
import { isMatch } from "./utils";
|
||||||
|
import { getWebfix, setWebfix } from "./storage";
|
||||||
|
import { apiFetch } from "../apis";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修复程序类型
|
||||||
|
*/
|
||||||
|
const WEBFIX_BR = "br";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 需要修复的站点列表
|
||||||
|
* - pattern 匹配网址
|
||||||
|
* - selector 需要修复的选择器
|
||||||
|
* - rootSlector 需要监听的选择器,可留空
|
||||||
|
* - fixer 修复函数,可针对不同网址,选用不同修复函数
|
||||||
|
*/
|
||||||
|
const DEFAULT_SITES = [
|
||||||
|
{
|
||||||
|
pattern: "www.phoronix.com",
|
||||||
|
selector: ".content",
|
||||||
|
rootSlector: "",
|
||||||
|
fixer: WEBFIX_BR,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "t.me/s/*",
|
||||||
|
selector: ".tgme_widget_message_text",
|
||||||
|
rootSlector: ".tgme_channel_history",
|
||||||
|
fixer: WEBFIX_BR,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修复过的标记
|
||||||
|
*/
|
||||||
|
const fixedSign = "kissfixed";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 采用 `br` 换行网站的修复函数
|
||||||
|
* 目标是将 `br` 替换成 `p`
|
||||||
|
* @param {*} node
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function brFixer(node) {
|
||||||
|
if (node.hasAttribute(fixedSign)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
node.setAttribute(fixedSign, "true");
|
||||||
|
|
||||||
|
var gapTags = ["BR", "WBR"];
|
||||||
|
var newlineTags = [
|
||||||
|
"DIV",
|
||||||
|
"UL",
|
||||||
|
"OL",
|
||||||
|
"LI",
|
||||||
|
"H1",
|
||||||
|
"H2",
|
||||||
|
"H3",
|
||||||
|
"H4",
|
||||||
|
"H5",
|
||||||
|
"H6",
|
||||||
|
"P",
|
||||||
|
"HR",
|
||||||
|
"PRE",
|
||||||
|
"TABLE",
|
||||||
|
];
|
||||||
|
|
||||||
|
var html = "";
|
||||||
|
node.childNodes.forEach(function (child, index) {
|
||||||
|
if (index === 0) {
|
||||||
|
html += "<p>";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gapTags.indexOf(child.nodeName) !== -1) {
|
||||||
|
html += "</p><p>";
|
||||||
|
} else if (newlineTags.indexOf(child.nodeName) !== -1) {
|
||||||
|
html += "</p>" + child.outerHTML + "<p>";
|
||||||
|
} else if (child.outerHTML) {
|
||||||
|
html += child.outerHTML;
|
||||||
|
} else if (child.nodeValue) {
|
||||||
|
html += child.nodeValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index === node.childNodes.length - 1) {
|
||||||
|
html += "</p>";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
node.innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修复程序映射
|
||||||
|
*/
|
||||||
|
const fixerMap = {
|
||||||
|
[WEBFIX_BR]: brFixer,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查找、监听节点,并执行修复函数
|
||||||
|
* @param {*} selector
|
||||||
|
* @param {*} fixer
|
||||||
|
* @param {*} rootSlector
|
||||||
|
*/
|
||||||
|
function run(selector, fixer, rootSlector) {
|
||||||
|
var mutaObserver = new MutationObserver(function (mutations) {
|
||||||
|
mutations.forEach(function (mutation) {
|
||||||
|
mutation.addedNodes.forEach(function (addNode) {
|
||||||
|
addNode.querySelectorAll(selector).forEach(fixer);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var rootNodes = [document];
|
||||||
|
if (rootSlector) {
|
||||||
|
rootNodes = document.querySelectorAll(rootSlector);
|
||||||
|
}
|
||||||
|
|
||||||
|
rootNodes.forEach(function (rootNode) {
|
||||||
|
rootNode.querySelectorAll(selector).forEach(fixer);
|
||||||
|
mutaObserver.observe(rootNode, {
|
||||||
|
childList: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步远程数据
|
||||||
|
* @param {*} url
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const syncWebfix = async (url) => {
|
||||||
|
const sites = await apiFetch(url);
|
||||||
|
await setWebfix(url, sites);
|
||||||
|
return sites;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从缓存或远程加载修复站点
|
||||||
|
* @param {*} url
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const loadOrFetchWebfix = async (url) => {
|
||||||
|
try {
|
||||||
|
let sites = await getWebfix(url);
|
||||||
|
if (sites?.length) {
|
||||||
|
return sites;
|
||||||
|
}
|
||||||
|
return syncWebfix(url);
|
||||||
|
} catch (err) {
|
||||||
|
console.log("[load webfix]", err.message);
|
||||||
|
return DEFAULT_SITES;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 匹配站点
|
||||||
|
*/
|
||||||
|
export async function webfix(href, { injectWebfix }) {
|
||||||
|
try {
|
||||||
|
if (!injectWebfix) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sites = await loadOrFetchWebfix(process.env.REACT_APP_WEBFIXURL);
|
||||||
|
for (var i = 0; i < sites.length; i++) {
|
||||||
|
var site = sites[i];
|
||||||
|
if (isMatch(href, site.pattern)) {
|
||||||
|
if (fixerMap[site.fixer]) {
|
||||||
|
run(site.selector, fixerMap[site.fixer], site.rootSlector);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`[kiss-webfix]: ${err.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,11 +10,12 @@ 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";
|
||||||
|
import { webfix } from "./libs/webfix";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 入口函数
|
* 入口函数
|
||||||
@@ -28,9 +29,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");
|
||||||
@@ -47,6 +51,7 @@ const init = async () => {
|
|||||||
const rules = await getRulesWithDefault();
|
const rules = await getRulesWithDefault();
|
||||||
const rule = await matchRule(rules, href, setting);
|
const rule = await matchRule(rules, href, setting);
|
||||||
const translator = new Translator(rule, setting);
|
const translator = new Translator(rule, setting);
|
||||||
|
webfix(href, setting);
|
||||||
|
|
||||||
if (isIframe) {
|
if (isIframe) {
|
||||||
// iframe
|
// iframe
|
||||||
@@ -88,28 +93,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);
|
||||||
};
|
};
|
||||||
@@ -118,9 +101,10 @@ const init = async () => {
|
|||||||
try {
|
try {
|
||||||
await init();
|
await init();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error("[KISS-Translator]", err);
|
||||||
const $err = document.createElement("div");
|
const $err = document.createElement("div");
|
||||||
$err.innerText = `KISS-Translator: ${err.message}`;
|
$err.innerText = `KISS-Translator: ${err.message}`;
|
||||||
$err.style.cssText = "background:red; color:#fff; z-index:10000;";
|
$err.style.cssText = "background:red; color:#fff;";
|
||||||
document.body.prepend($err);
|
document.body.prepend($err);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -1,16 +1,22 @@
|
|||||||
import Paper from "@mui/material/Paper";
|
import Paper from "@mui/material/Paper";
|
||||||
import Box from "@mui/material/Box";
|
|
||||||
import Fab from "@mui/material/Fab";
|
import Fab from "@mui/material/Fab";
|
||||||
import TranslateIcon from "@mui/icons-material/Translate";
|
import TranslateIcon from "@mui/icons-material/Translate";
|
||||||
import ThemeProvider from "../../hooks/Theme";
|
import ThemeProvider from "../../hooks/Theme";
|
||||||
import Draggable from "./Draggable";
|
import Draggable from "./Draggable";
|
||||||
import IconButton from "@mui/material/IconButton";
|
|
||||||
import CloseIcon from "@mui/icons-material/Close";
|
|
||||||
import Stack from "@mui/material/Stack";
|
|
||||||
import { useEffect, useState, useMemo, useCallback } from "react";
|
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 { isGm } from "../../libs/client";
|
||||||
|
import Header from "../Popup/Header";
|
||||||
|
import {
|
||||||
|
DEFAULT_SHORTCUTS,
|
||||||
|
OPT_SHORTCUT_TRANSLATE,
|
||||||
|
OPT_SHORTCUT_STYLE,
|
||||||
|
OPT_SHORTCUT_POPUP,
|
||||||
|
OPT_SHORTCUT_SETTING,
|
||||||
|
} from "../../config";
|
||||||
|
import { shortcutRegister } from "../../libs/shortcut";
|
||||||
|
|
||||||
export default function Action({ translator, fab }) {
|
export default function Action({ translator, fab }) {
|
||||||
const fabWidth = 40;
|
const fabWidth = 40;
|
||||||
@@ -44,6 +50,88 @@ export default function Action({ translator, fab }) {
|
|||||||
setMoved(true);
|
setMoved(true);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// 注册快捷键
|
||||||
|
const shortcuts = translator.setting.shortcuts || DEFAULT_SHORTCUTS;
|
||||||
|
const clearShortcuts = [
|
||||||
|
shortcutRegister(shortcuts[OPT_SHORTCUT_TRANSLATE], () => {
|
||||||
|
translator.toggle();
|
||||||
|
setShowPopup(false);
|
||||||
|
}),
|
||||||
|
shortcutRegister(shortcuts[OPT_SHORTCUT_STYLE], () => {
|
||||||
|
translator.toggleStyle();
|
||||||
|
setShowPopup(false);
|
||||||
|
}),
|
||||||
|
shortcutRegister(shortcuts[OPT_SHORTCUT_POPUP], () => {
|
||||||
|
setShowPopup((pre) => !pre);
|
||||||
|
}),
|
||||||
|
shortcutRegister(shortcuts[OPT_SHORTCUT_SETTING], () => {
|
||||||
|
window.open(process.env.REACT_APP_OPTIONSPAGE, "_blank");
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearShortcuts.forEach((fn) => {
|
||||||
|
fn();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}, [translator]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// 注册菜单
|
||||||
|
const menuCommandIds = [];
|
||||||
|
if (isGm) {
|
||||||
|
try {
|
||||||
|
menuCommandIds.push(
|
||||||
|
GM.registerMenuCommand(
|
||||||
|
"Toggle Translate (Alt+q)",
|
||||||
|
(event) => {
|
||||||
|
translator.toggle();
|
||||||
|
setShowPopup(false);
|
||||||
|
},
|
||||||
|
"Q"
|
||||||
|
),
|
||||||
|
GM.registerMenuCommand(
|
||||||
|
"Toggle Style (Alt+c)",
|
||||||
|
(event) => {
|
||||||
|
translator.toggleStyle();
|
||||||
|
setShowPopup(false);
|
||||||
|
},
|
||||||
|
"C"
|
||||||
|
),
|
||||||
|
GM.registerMenuCommand(
|
||||||
|
"Open Menu (Alt+k)",
|
||||||
|
(event) => {
|
||||||
|
setShowPopup((pre) => !pre);
|
||||||
|
},
|
||||||
|
"K"
|
||||||
|
),
|
||||||
|
GM.registerMenuCommand(
|
||||||
|
"Open Setting (Alt+o)",
|
||||||
|
(event) => {
|
||||||
|
setShowPopup((pre) => !pre);
|
||||||
|
},
|
||||||
|
"O"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} 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 +141,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);
|
||||||
};
|
};
|
||||||
@@ -91,23 +180,7 @@ export default function Action({ translator, fab }) {
|
|||||||
onMove={handleMove}
|
onMove={handleMove}
|
||||||
handler={
|
handler={
|
||||||
<Paper style={{ cursor: "move" }} elevation={3}>
|
<Paper style={{ cursor: "move" }} elevation={3}>
|
||||||
<Stack
|
<Header setShowPopup={setShowPopup} />
|
||||||
direction="row"
|
|
||||||
justifyContent="space-between"
|
|
||||||
alignItems="center"
|
|
||||||
spacing={2}
|
|
||||||
>
|
|
||||||
<Box style={{ marginLeft: 16 }}>
|
|
||||||
{`${process.env.REACT_APP_NAME} v${process.env.REACT_APP_VERSION}`}
|
|
||||||
</Box>
|
|
||||||
<IconButton
|
|
||||||
onClick={() => {
|
|
||||||
setShowPopup(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CloseIcon />
|
|
||||||
</IconButton>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
</Paper>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -121,7 +194,7 @@ export default function Action({ translator, fab }) {
|
|||||||
key="fab"
|
key="fab"
|
||||||
snapEdge
|
snapEdge
|
||||||
{...fabProps}
|
{...fabProps}
|
||||||
show={!showPopup}
|
show={translator.setting.hideFab ? false : !showPopup}
|
||||||
onStart={handleStart}
|
onStart={handleStart}
|
||||||
onMove={handleMove}
|
onMove={handleMove}
|
||||||
handler={
|
handler={
|
||||||
|
|||||||
@@ -9,15 +9,15 @@ import {
|
|||||||
OPT_STYLE_HIGHLIGHT,
|
OPT_STYLE_HIGHLIGHT,
|
||||||
OPT_STYLE_DIY,
|
OPT_STYLE_DIY,
|
||||||
DEFAULT_COLOR,
|
DEFAULT_COLOR,
|
||||||
EVENT_KISS,
|
|
||||||
MSG_TRANS_CURRULE,
|
MSG_TRANS_CURRULE,
|
||||||
TRANS_NEWLINE_LENGTH,
|
TRANS_NEWLINE_LENGTH,
|
||||||
} from "../../config";
|
} from "../../config";
|
||||||
import { useTranslate } from "../../hooks/Translate";
|
import { useTranslate } from "../../hooks/Translate";
|
||||||
import styled from "styled-components";
|
import { styled } from "@mui/material/styles";
|
||||||
|
|
||||||
const LineSpan = styled.span`
|
const LineSpan = styled("span")`
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
|
-webkit-opacity: 0.6;
|
||||||
text-decoration-line: underline;
|
text-decoration-line: underline;
|
||||||
text-decoration-style: ${(props) => props.$lineStyle};
|
text-decoration-style: ${(props) => props.$lineStyle};
|
||||||
text-decoration-color: ${(props) => props.$lineColor};
|
text-decoration-color: ${(props) => props.$lineColor};
|
||||||
@@ -30,26 +30,25 @@ const LineSpan = styled.span`
|
|||||||
-webkit-text-underline-offset: 0.3em;
|
-webkit-text-underline-offset: 0.3em;
|
||||||
&:hover {
|
&:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
-webkit-opacity: 1;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const FuzzySpan = styled.span`
|
const FuzzySpan = styled("span")`
|
||||||
filter: blur(5px);
|
filter: blur(0.2em);
|
||||||
transition: filter 0.2s ease-in-out;
|
-webkit-filter: blur(0.2em);
|
||||||
&hover: {
|
&:hover {
|
||||||
filter: none;
|
filter: none;
|
||||||
|
-webkit-filter: none;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const HighlightSpan = styled.span`
|
const HighlightSpan = styled("span")`
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background-color: ${(props) => props.$bgColor};
|
background-color: ${(props) => props.$bgColor};
|
||||||
&hover: {
|
|
||||||
filter: none;
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const DiySpan = styled.span`
|
const DiySpan = styled("span")`
|
||||||
${(props) => props.$diyStyle}
|
${(props) => props.$diyStyle}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -112,11 +111,11 @@ export default function Content({ q, translator }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
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]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
174
src/views/Options/Apis.js
Normal file
174
src/views/Options/Apis.js
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
import Stack from "@mui/material/Stack";
|
||||||
|
import TextField from "@mui/material/TextField";
|
||||||
|
import Button from "@mui/material/Button";
|
||||||
|
import CircularProgress from "@mui/material/CircularProgress";
|
||||||
|
import {
|
||||||
|
OPT_TRANS_ALL,
|
||||||
|
OPT_TRANS_MICROSOFT,
|
||||||
|
OPT_TRANS_OPENAI,
|
||||||
|
OPT_TRANS_CUSTOMIZE,
|
||||||
|
URL_KISS_PROXY,
|
||||||
|
} from "../../config";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useI18n } from "../../hooks/I18n";
|
||||||
|
import Typography from "@mui/material/Typography";
|
||||||
|
import Accordion from "@mui/material/Accordion";
|
||||||
|
import AccordionSummary from "@mui/material/AccordionSummary";
|
||||||
|
import AccordionDetails from "@mui/material/AccordionDetails";
|
||||||
|
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
||||||
|
import Alert from "@mui/material/Alert";
|
||||||
|
import { useAlert } from "../../hooks/Alert";
|
||||||
|
import { useApi } from "../../hooks/Api";
|
||||||
|
import { apiTranslate } from "../../apis";
|
||||||
|
import Box from "@mui/material/Box";
|
||||||
|
import Link from "@mui/material/Link";
|
||||||
|
|
||||||
|
function TestButton({ translator, api }) {
|
||||||
|
const i18n = useI18n();
|
||||||
|
const alert = useAlert();
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const handleApiTest = async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
const [text] = await apiTranslate({
|
||||||
|
translator,
|
||||||
|
text: "hello world",
|
||||||
|
fromLang: "en",
|
||||||
|
toLang: "zh-CN",
|
||||||
|
apiSetting: { ...api, useCache: false },
|
||||||
|
});
|
||||||
|
if (!text) {
|
||||||
|
throw new Error("empty reault");
|
||||||
|
}
|
||||||
|
alert.success(i18n("test_success"));
|
||||||
|
} catch (err) {
|
||||||
|
alert.error(`${i18n("test_failed")}: ${err.message}`);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <CircularProgress size={16} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button size="small" variant="contained" onClick={handleApiTest}>
|
||||||
|
{i18n("click_test")}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ApiFields({ translator }) {
|
||||||
|
const i18n = useI18n();
|
||||||
|
const { api, updateApi, resetApi } = useApi(translator);
|
||||||
|
const { url = "", key = "", model = "", prompt = "" } = api;
|
||||||
|
|
||||||
|
const handleChange = (e) => {
|
||||||
|
const { name, value } = e.target;
|
||||||
|
updateApi({
|
||||||
|
[name]: value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack spacing={3}>
|
||||||
|
{translator !== OPT_TRANS_MICROSOFT && (
|
||||||
|
<>
|
||||||
|
<TextField
|
||||||
|
size="small"
|
||||||
|
label={"URL"}
|
||||||
|
name="url"
|
||||||
|
value={url}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
size="small"
|
||||||
|
label={"KEY"}
|
||||||
|
name="key"
|
||||||
|
value={key}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{translator === OPT_TRANS_OPENAI && (
|
||||||
|
<>
|
||||||
|
<TextField
|
||||||
|
size="small"
|
||||||
|
label={"MODEL"}
|
||||||
|
name="model"
|
||||||
|
value={model}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
size="small"
|
||||||
|
label={"PROMPT"}
|
||||||
|
name="prompt"
|
||||||
|
value={prompt}
|
||||||
|
onChange={handleChange}
|
||||||
|
multiline
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Stack direction="row" spacing={2}>
|
||||||
|
<TestButton translator={translator} api={api} />
|
||||||
|
{translator !== OPT_TRANS_MICROSOFT && (
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
variant="outlined"
|
||||||
|
onClick={() => {
|
||||||
|
resetApi();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{i18n("restore_default")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
{translator === OPT_TRANS_CUSTOMIZE && (
|
||||||
|
<pre>{i18n("custom_api_help")}</pre>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ApiAccordion({ translator }) {
|
||||||
|
const [expanded, setExpanded] = useState(false);
|
||||||
|
|
||||||
|
const handleChange = (e) => {
|
||||||
|
setExpanded((pre) => !pre);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Accordion expanded={expanded} onChange={handleChange}>
|
||||||
|
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||||
|
<Typography>{translator}</Typography>
|
||||||
|
</AccordionSummary>
|
||||||
|
<AccordionDetails>
|
||||||
|
{expanded && <ApiFields translator={translator} />}
|
||||||
|
</AccordionDetails>
|
||||||
|
</Accordion>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Apis() {
|
||||||
|
const i18n = useI18n();
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Stack spacing={3}>
|
||||||
|
<Alert severity="info">
|
||||||
|
<Link href={URL_KISS_PROXY} target="_blank">
|
||||||
|
{i18n("about_api_proxy")}
|
||||||
|
</Link>
|
||||||
|
</Alert>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
{OPT_TRANS_ALL.map((translator) => (
|
||||||
|
<ApiAccordion key={translator} translator={translator} />
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
13
src/views/Options/DarkModeButton.js
Normal file
13
src/views/Options/DarkModeButton.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import IconButton from "@mui/material/IconButton";
|
||||||
|
import { useDarkMode } from "../../hooks/ColorMode";
|
||||||
|
import LightModeIcon from "@mui/icons-material/LightMode";
|
||||||
|
import DarkModeIcon from "@mui/icons-material/DarkMode";
|
||||||
|
|
||||||
|
export default function DarkModeButton() {
|
||||||
|
const { darkMode, toggleDarkMode } = useDarkMode();
|
||||||
|
return (
|
||||||
|
<IconButton onClick={toggleDarkMode} color="inherit">
|
||||||
|
{darkMode ? <LightModeIcon /> : <DarkModeIcon />}
|
||||||
|
</IconButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -4,16 +4,13 @@ import IconButton from "@mui/material/IconButton";
|
|||||||
import MenuIcon from "@mui/icons-material/Menu";
|
import MenuIcon from "@mui/icons-material/Menu";
|
||||||
import Toolbar from "@mui/material/Toolbar";
|
import Toolbar from "@mui/material/Toolbar";
|
||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
import { useDarkMode } from "../../hooks/ColorMode";
|
|
||||||
import LightModeIcon from "@mui/icons-material/LightMode";
|
|
||||||
import DarkModeIcon from "@mui/icons-material/DarkMode";
|
|
||||||
import Link from "@mui/material/Link";
|
import Link from "@mui/material/Link";
|
||||||
import { useI18n } from "../../hooks/I18n";
|
import { useI18n } from "../../hooks/I18n";
|
||||||
|
import DarkModeButton from "./DarkModeButton";
|
||||||
|
|
||||||
function Header(props) {
|
function Header(props) {
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const { onDrawerToggle } = props;
|
const { onDrawerToggle } = props;
|
||||||
const { darkMode, toggleDarkMode } = useDarkMode();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppBar
|
<AppBar
|
||||||
@@ -39,11 +36,10 @@ function Header(props) {
|
|||||||
underline="none"
|
underline="none"
|
||||||
color="inherit"
|
color="inherit"
|
||||||
href={process.env.REACT_APP_HOMEPAGE}
|
href={process.env.REACT_APP_HOMEPAGE}
|
||||||
|
target="_blank"
|
||||||
>{`${i18n("app_name")} v${process.env.REACT_APP_VERSION}`}</Link>
|
>{`${i18n("app_name")} v${process.env.REACT_APP_VERSION}`}</Link>
|
||||||
</Box>
|
</Box>
|
||||||
<IconButton onClick={toggleDarkMode} color="inherit">
|
<DarkModeButton />
|
||||||
{darkMode ? <LightModeIcon /> : <DarkModeIcon />}
|
|
||||||
</IconButton>
|
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
</AppBar>
|
</AppBar>
|
||||||
);
|
);
|
||||||
|
|||||||
19
src/views/Options/HelpButton.js
Normal file
19
src/views/Options/HelpButton.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import Button from "@mui/material/Button";
|
||||||
|
import { useI18n } from "../../hooks/I18n";
|
||||||
|
import HelpIcon from "@mui/icons-material/Help";
|
||||||
|
|
||||||
|
export default function HelpButton({ url }) {
|
||||||
|
const i18n = useI18n();
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
variant="outlined"
|
||||||
|
onClick={() => {
|
||||||
|
window.open(url, "_blank");
|
||||||
|
}}
|
||||||
|
startIcon={<HelpIcon />}
|
||||||
|
>
|
||||||
|
{i18n("help")}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -10,6 +10,8 @@ import InfoIcon from "@mui/icons-material/Info";
|
|||||||
import DesignServicesIcon from "@mui/icons-material/DesignServices";
|
import DesignServicesIcon from "@mui/icons-material/DesignServices";
|
||||||
import { useI18n } from "../../hooks/I18n";
|
import { useI18n } from "../../hooks/I18n";
|
||||||
import SyncIcon from "@mui/icons-material/Sync";
|
import SyncIcon from "@mui/icons-material/Sync";
|
||||||
|
import ApiIcon from "@mui/icons-material/Api";
|
||||||
|
import SendTimeExtensionIcon from "@mui/icons-material/SendTimeExtension";
|
||||||
|
|
||||||
function LinkItem({ label, url, icon }) {
|
function LinkItem({ label, url, icon }) {
|
||||||
const match = useMatch(url);
|
const match = useMatch(url);
|
||||||
@@ -36,12 +38,24 @@ export default function Navigator(props) {
|
|||||||
url: "/rules",
|
url: "/rules",
|
||||||
icon: <DesignServicesIcon />,
|
icon: <DesignServicesIcon />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "apis_setting",
|
||||||
|
label: i18n("apis_setting"),
|
||||||
|
url: "/apis",
|
||||||
|
icon: <ApiIcon />,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "sync",
|
id: "sync",
|
||||||
label: i18n("sync_setting"),
|
label: i18n("sync_setting"),
|
||||||
url: "/sync",
|
url: "/sync",
|
||||||
icon: <SyncIcon />,
|
icon: <SyncIcon />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "webfix",
|
||||||
|
label: i18n("patch_setting"),
|
||||||
|
url: "/webfix",
|
||||||
|
icon: <SendTimeExtensionIcon />,
|
||||||
|
},
|
||||||
{ id: "about", label: i18n("about"), url: "/about", icon: <InfoIcon /> },
|
{ id: "about", label: i18n("about"), url: "/about", icon: <InfoIcon /> },
|
||||||
];
|
];
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
OPT_STYLE_ALL,
|
OPT_STYLE_ALL,
|
||||||
OPT_STYLE_DIY,
|
OPT_STYLE_DIY,
|
||||||
OPT_STYLE_USE_COLOR,
|
OPT_STYLE_USE_COLOR,
|
||||||
|
URL_KISS_RULES_NEW_ISSUE,
|
||||||
} 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";
|
||||||
@@ -45,6 +46,8 @@ 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";
|
import OwSubRule from "./OwSubRule";
|
||||||
|
import ClearAllIcon from "@mui/icons-material/ClearAll";
|
||||||
|
import HelpButton from "./HelpButton";
|
||||||
|
|
||||||
function RuleFields({ rule, rules, setShow, setKeyword }) {
|
function RuleFields({ rule, rules, setShow, setKeyword }) {
|
||||||
const initFormValues = rule || {
|
const initFormValues = rule || {
|
||||||
@@ -470,7 +473,7 @@ function ShareButton({ rules, injectRules, selectedUrl }) {
|
|||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
startIcon={<ShareIcon />}
|
startIcon={<ShareIcon />}
|
||||||
>
|
>
|
||||||
{"分享"}
|
{i18n("share")}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -552,6 +555,19 @@ function UserRules({ subRules }) {
|
|||||||
selectedUrl={selectedUrl}
|
selectedUrl={selectedUrl}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
variant="outlined"
|
||||||
|
onClick={() => {
|
||||||
|
rules.clear();
|
||||||
|
}}
|
||||||
|
startIcon={<ClearAllIcon />}
|
||||||
|
>
|
||||||
|
{i18n("clear_all")}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<HelpButton url={URL_KISS_RULES_NEW_ISSUE} />
|
||||||
|
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
control={
|
control={
|
||||||
<Switch
|
<Switch
|
||||||
@@ -599,7 +615,15 @@ function UserRules({ subRules }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SubRulesItem({ index, url, selectedUrl, delSub, setSelectedRules }) {
|
function SubRulesItem({
|
||||||
|
index,
|
||||||
|
url,
|
||||||
|
syncAt,
|
||||||
|
selectedUrl,
|
||||||
|
delSub,
|
||||||
|
updateSub,
|
||||||
|
setSelectedRules,
|
||||||
|
}) {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const handleDel = async () => {
|
const handleDel = async () => {
|
||||||
@@ -618,6 +642,7 @@ function SubRulesItem({ index, url, selectedUrl, delSub, setSelectedRules }) {
|
|||||||
if (rules.length > 0 && url === selectedUrl) {
|
if (rules.length > 0 && url === selectedUrl) {
|
||||||
setSelectedRules(rules);
|
setSelectedRules(rules);
|
||||||
}
|
}
|
||||||
|
await updateSub(url, { syncAt: Date.now() });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("[sync sub rules]", err);
|
console.log("[sync sub rules]", err);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -629,6 +654,12 @@ function SubRulesItem({ index, url, selectedUrl, delSub, setSelectedRules }) {
|
|||||||
<Stack direction="row" alignItems="center" spacing={2}>
|
<Stack direction="row" alignItems="center" spacing={2}>
|
||||||
<FormControlLabel value={url} control={<Radio />} label={url} />
|
<FormControlLabel value={url} control={<Radio />} label={url} />
|
||||||
|
|
||||||
|
{syncAt && (
|
||||||
|
<span style={{ marginLeft: "0.5em", opacity: 0.5 }}>
|
||||||
|
[{new Date(syncAt).toLocaleString()}]
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<CircularProgress size={16} />
|
<CircularProgress size={16} />
|
||||||
) : (
|
) : (
|
||||||
@@ -715,6 +746,7 @@ function SubRulesEdit({ subList, addSub }) {
|
|||||||
>
|
>
|
||||||
{i18n("add")}
|
{i18n("add")}
|
||||||
</Button>
|
</Button>
|
||||||
|
<HelpButton url={URL_KISS_RULES_NEW_ISSUE} />
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
{showInput && (
|
{showInput && (
|
||||||
@@ -752,6 +784,7 @@ function SubRules({ subRules }) {
|
|||||||
const {
|
const {
|
||||||
subList,
|
subList,
|
||||||
selectSub,
|
selectSub,
|
||||||
|
updateSub,
|
||||||
addSub,
|
addSub,
|
||||||
delSub,
|
delSub,
|
||||||
selectedUrl,
|
selectedUrl,
|
||||||
@@ -774,9 +807,11 @@ function SubRules({ subRules }) {
|
|||||||
<SubRulesItem
|
<SubRulesItem
|
||||||
key={item.url}
|
key={item.url}
|
||||||
url={item.url}
|
url={item.url}
|
||||||
|
syncAt={item.syncAt}
|
||||||
index={index}
|
index={index}
|
||||||
selectedUrl={selectedUrl}
|
selectedUrl={selectedUrl}
|
||||||
delSub={delSub}
|
delSub={delSub}
|
||||||
|
updateSub={updateSub}
|
||||||
setSelectedRules={setSelectedRules}
|
setSelectedRules={setSelectedRules}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -6,14 +6,84 @@ 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 Link from "@mui/material/Link";
|
||||||
|
import FormHelperText from "@mui/material/FormHelperText";
|
||||||
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, URL_KISS_PROXY, TRANS_NEWLINE_LENGTH } from "../../config";
|
import { useAlert } from "../../hooks/Alert";
|
||||||
|
import { isExt } from "../../libs/client";
|
||||||
|
import IconButton from "@mui/material/IconButton";
|
||||||
|
import EditIcon from "@mui/icons-material/Edit";
|
||||||
|
import Grid from "@mui/material/Grid";
|
||||||
|
import {
|
||||||
|
UI_LANGS,
|
||||||
|
TRANS_NEWLINE_LENGTH,
|
||||||
|
CACHE_NAME,
|
||||||
|
OPT_MOUSEKEY_ALL,
|
||||||
|
OPT_MOUSEKEY_DISABLE,
|
||||||
|
OPT_SHORTCUT_TRANSLATE,
|
||||||
|
OPT_SHORTCUT_STYLE,
|
||||||
|
OPT_SHORTCUT_POPUP,
|
||||||
|
OPT_SHORTCUT_SETTING,
|
||||||
|
} from "../../config";
|
||||||
|
import { useEffect, useState, useRef } from "react";
|
||||||
|
import { useShortcut } from "../../hooks/Shortcut";
|
||||||
|
import { shortcutListener } from "../../libs/shortcut";
|
||||||
|
|
||||||
|
function ShortcutItem({ action, label }) {
|
||||||
|
const { shortcut, setShortcut } = useShortcut(action);
|
||||||
|
const [disabled, setDisabled] = useState(true);
|
||||||
|
const inputRef = useRef(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (disabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
inputRef.current.focus();
|
||||||
|
setShortcut([]);
|
||||||
|
|
||||||
|
const clearShortcut = shortcutListener((curkeys, allkeys) => {
|
||||||
|
setShortcut(allkeys);
|
||||||
|
if (curkeys.length === 0) {
|
||||||
|
setDisabled(true);
|
||||||
|
}
|
||||||
|
}, inputRef.current);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearShortcut();
|
||||||
|
};
|
||||||
|
}, [disabled, setShortcut]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack direction="row">
|
||||||
|
<TextField
|
||||||
|
size="small"
|
||||||
|
label={label}
|
||||||
|
name={label}
|
||||||
|
value={shortcut.join(" + ")}
|
||||||
|
fullWidth
|
||||||
|
inputRef={inputRef}
|
||||||
|
disabled={disabled}
|
||||||
|
onBlur={() => {
|
||||||
|
setDisabled(true);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
setDisabled(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{<EditIcon />}
|
||||||
|
</IconButton>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default function Settings() {
|
export default function Settings() {
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const { setting, updateSetting } = useSetting();
|
const { setting, updateSetting } = useSetting();
|
||||||
|
const alert = useAlert();
|
||||||
|
|
||||||
const handleChange = (e) => {
|
const handleChange = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -41,19 +111,25 @@ export default function Settings() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleClearCache = () => {
|
||||||
|
try {
|
||||||
|
caches.delete(CACHE_NAME);
|
||||||
|
alert.success(i18n("clear_success"));
|
||||||
|
} catch (err) {
|
||||||
|
console.log("[clear cache]", err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const {
|
const {
|
||||||
uiLang,
|
uiLang,
|
||||||
googleUrl,
|
|
||||||
fetchLimit,
|
fetchLimit,
|
||||||
fetchInterval,
|
fetchInterval,
|
||||||
minLength,
|
minLength,
|
||||||
maxLength,
|
maxLength,
|
||||||
openaiUrl,
|
|
||||||
openaiKey,
|
|
||||||
openaiModel,
|
|
||||||
openaiPrompt,
|
|
||||||
clearCache,
|
clearCache,
|
||||||
newlineLength = TRANS_NEWLINE_LENGTH,
|
newlineLength = TRANS_NEWLINE_LENGTH,
|
||||||
|
mouseKey = OPT_MOUSEKEY_DISABLE,
|
||||||
|
hideFab = false,
|
||||||
} = setting;
|
} = setting;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -121,65 +197,81 @@ export default function Settings() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<FormControl size="small">
|
<FormControl size="small">
|
||||||
<InputLabel>{i18n("clear_cache")}</InputLabel>
|
<InputLabel>{i18n("mouseover_translation")}</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
name="clearCache"
|
name="mouseKey"
|
||||||
value={clearCache}
|
value={mouseKey}
|
||||||
label={i18n("clear_cache")}
|
label={i18n("mouseover_translation")}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
>
|
>
|
||||||
<MenuItem value={false}>{i18n("clear_cache_never")}</MenuItem>
|
{OPT_MOUSEKEY_ALL.map((item) => (
|
||||||
<MenuItem value={true}>{i18n("clear_cache_restart")}</MenuItem>
|
<MenuItem key={item} value={item}>
|
||||||
|
{i18n(item)}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<TextField
|
{isExt ? (
|
||||||
size="small"
|
<FormControl size="small">
|
||||||
label={i18n("google_api")}
|
<InputLabel>{i18n("if_clear_cache")}</InputLabel>
|
||||||
name="googleUrl"
|
<Select
|
||||||
value={googleUrl}
|
name="clearCache"
|
||||||
onChange={handleChange}
|
value={clearCache}
|
||||||
helperText={
|
label={i18n("if_clear_cache")}
|
||||||
<Link href={URL_KISS_PROXY}>{i18n("about_api_proxy")}</Link>
|
onChange={handleChange}
|
||||||
}
|
>
|
||||||
/>
|
<MenuItem value={false}>{i18n("clear_cache_never")}</MenuItem>
|
||||||
|
<MenuItem value={true}>{i18n("clear_cache_restart")}</MenuItem>
|
||||||
<TextField
|
</Select>
|
||||||
size="small"
|
<FormHelperText>
|
||||||
label={i18n("openai_api")}
|
<Link component="button" onClick={handleClearCache}>
|
||||||
name="openaiUrl"
|
{i18n("clear_all_cache_now")}
|
||||||
value={openaiUrl}
|
</Link>
|
||||||
onChange={handleChange}
|
</FormHelperText>
|
||||||
helperText={
|
</FormControl>
|
||||||
<Link href={URL_KISS_PROXY}>{i18n("about_api_proxy")}</Link>
|
) : (
|
||||||
}
|
<>
|
||||||
/>
|
<FormControl size="small">
|
||||||
|
<InputLabel>{i18n("hide_fab_button")}</InputLabel>
|
||||||
<TextField
|
<Select
|
||||||
size="small"
|
name="hideFab"
|
||||||
type="password"
|
value={hideFab}
|
||||||
label={i18n("openai_key")}
|
label={i18n("hide_fab_button")}
|
||||||
name="openaiKey"
|
onChange={handleChange}
|
||||||
value={openaiKey}
|
>
|
||||||
onChange={handleChange}
|
<MenuItem value={false}>{i18n("show")}</MenuItem>
|
||||||
/>
|
<MenuItem value={true}>{i18n("hide")}</MenuItem>
|
||||||
|
</Select>
|
||||||
<TextField
|
</FormControl>
|
||||||
size="small"
|
<Grid container rowSpacing={2} columns={12}>
|
||||||
label={i18n("openai_model")}
|
<Grid item xs={12} sm={12} md={3} lg={3}>
|
||||||
name="openaiModel"
|
<ShortcutItem
|
||||||
value={openaiModel}
|
action={OPT_SHORTCUT_TRANSLATE}
|
||||||
onChange={handleChange}
|
label={i18n("toggle_translate_shortcut")}
|
||||||
/>
|
/>
|
||||||
|
</Grid>
|
||||||
<TextField
|
<Grid item xs={12} sm={12} md={3} lg={3}>
|
||||||
size="small"
|
<ShortcutItem
|
||||||
label={i18n("openai_prompt")}
|
action={OPT_SHORTCUT_STYLE}
|
||||||
name="openaiPrompt"
|
label={i18n("toggle_style_shortcut")}
|
||||||
value={openaiPrompt}
|
/>
|
||||||
onChange={handleChange}
|
</Grid>
|
||||||
multiline
|
<Grid item xs={12} sm={12} md={3} lg={3}>
|
||||||
/>
|
<ShortcutItem
|
||||||
|
action={OPT_SHORTCUT_POPUP}
|
||||||
|
label={i18n("toggle_popup_shortcut")}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} sm={12} md={3} lg={3}>
|
||||||
|
<ShortcutItem
|
||||||
|
action={OPT_SHORTCUT_SETTING}
|
||||||
|
label={i18n("open_setting_shortcut")}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -35,10 +35,10 @@ export default function SyncSetting() {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
await syncSettingAndRules();
|
await syncSettingAndRules();
|
||||||
await reloadSetting();
|
await reloadSetting();
|
||||||
alert.success(i18n("data_sync_success"));
|
alert.success(i18n("sync_success"));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("[sync all]", err);
|
console.log("[sync all]", err);
|
||||||
alert.error(i18n("data_sync_error"));
|
alert.error(i18n("sync_failed"));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -58,7 +58,9 @@ export default function SyncSetting() {
|
|||||||
value={syncUrl}
|
value={syncUrl}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
helperText={
|
helperText={
|
||||||
<Link href={URL_KISS_WORKER}>{i18n("about_sync_api")}</Link>
|
<Link href={URL_KISS_WORKER} target="_blank">
|
||||||
|
{i18n("about_sync_api")}
|
||||||
|
</Link>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -85,7 +87,7 @@ export default function SyncSetting() {
|
|||||||
onClick={handleSyncTest}
|
onClick={handleSyncTest}
|
||||||
startIcon={<SyncIcon />}
|
startIcon={<SyncIcon />}
|
||||||
>
|
>
|
||||||
{i18n("data_sync_test")}
|
{i18n("sync_now")}
|
||||||
</Button>
|
</Button>
|
||||||
{loading && <CircularProgress size={16} />}
|
{loading && <CircularProgress size={16} />}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
160
src/views/Options/Webfix.js
Normal file
160
src/views/Options/Webfix.js
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
import Stack from "@mui/material/Stack";
|
||||||
|
import TextField from "@mui/material/TextField";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useI18n } from "../../hooks/I18n";
|
||||||
|
import Typography from "@mui/material/Typography";
|
||||||
|
import Accordion from "@mui/material/Accordion";
|
||||||
|
import AccordionSummary from "@mui/material/AccordionSummary";
|
||||||
|
import AccordionDetails from "@mui/material/AccordionDetails";
|
||||||
|
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
||||||
|
import Alert from "@mui/material/Alert";
|
||||||
|
import Box from "@mui/material/Box";
|
||||||
|
import FormControlLabel from "@mui/material/FormControlLabel";
|
||||||
|
import Switch from "@mui/material/Switch";
|
||||||
|
import { useSetting } from "../../hooks/Setting";
|
||||||
|
import CircularProgress from "@mui/material/CircularProgress";
|
||||||
|
import { syncWebfix, loadOrFetchWebfix } from "../../libs/webfix";
|
||||||
|
import Button from "@mui/material/Button";
|
||||||
|
import SyncIcon from "@mui/icons-material/Sync";
|
||||||
|
import { useAlert } from "../../hooks/Alert";
|
||||||
|
import HelpButton from "./HelpButton";
|
||||||
|
import { URL_KISS_RULES_NEW_ISSUE } from "../../config";
|
||||||
|
|
||||||
|
function ApiFields({ site }) {
|
||||||
|
const { selector, rootSlector, fixer } = site;
|
||||||
|
return (
|
||||||
|
<Stack spacing={3}>
|
||||||
|
<TextField
|
||||||
|
size="small"
|
||||||
|
label={"rootSlector"}
|
||||||
|
name="rootSlector"
|
||||||
|
value={rootSlector || "document"}
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
size="small"
|
||||||
|
label={"selector"}
|
||||||
|
name="selector"
|
||||||
|
value={selector}
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
size="small"
|
||||||
|
label={"fixer"}
|
||||||
|
name="fixer"
|
||||||
|
value={fixer}
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ApiAccordion({ site }) {
|
||||||
|
const [expanded, setExpanded] = useState(false);
|
||||||
|
|
||||||
|
const handleChange = (e) => {
|
||||||
|
setExpanded((pre) => !pre);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Accordion expanded={expanded} onChange={handleChange}>
|
||||||
|
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||||
|
<Typography>{site.pattern}</Typography>
|
||||||
|
</AccordionSummary>
|
||||||
|
<AccordionDetails>
|
||||||
|
{expanded && <ApiFields site={site} />}
|
||||||
|
</AccordionDetails>
|
||||||
|
</Accordion>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Webfix() {
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [sites, setSites] = useState([]);
|
||||||
|
const i18n = useI18n();
|
||||||
|
const alert = useAlert();
|
||||||
|
const { setting, updateSetting } = useSetting();
|
||||||
|
|
||||||
|
const handleSyncTest = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
await syncWebfix(process.env.REACT_APP_WEBFIXURL);
|
||||||
|
alert.success(i18n("sync_success"));
|
||||||
|
} catch (err) {
|
||||||
|
console.log("[sync webfix]", err);
|
||||||
|
alert.error(i18n("sync_failed"));
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
const sites = await loadOrFetchWebfix(process.env.REACT_APP_WEBFIXURL);
|
||||||
|
setSites(sites);
|
||||||
|
} catch (err) {
|
||||||
|
console.log("[load webfix]", err.message);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Stack spacing={3}>
|
||||||
|
<Alert severity="info">{i18n("patch_setting_help")}</Alert>
|
||||||
|
|
||||||
|
<Stack
|
||||||
|
direction="row"
|
||||||
|
alignItems="center"
|
||||||
|
spacing={2}
|
||||||
|
useFlexGap
|
||||||
|
flexWrap="wrap"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
variant="outlined"
|
||||||
|
disabled={loading}
|
||||||
|
onClick={handleSyncTest}
|
||||||
|
startIcon={<SyncIcon />}
|
||||||
|
>
|
||||||
|
{i18n("sync_now")}
|
||||||
|
</Button>
|
||||||
|
<HelpButton url={URL_KISS_RULES_NEW_ISSUE} />
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Switch
|
||||||
|
size="small"
|
||||||
|
checked={!!setting.injectWebfix}
|
||||||
|
onChange={() => {
|
||||||
|
updateSetting({
|
||||||
|
injectWebfix: !setting.injectWebfix,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={i18n("inject_webfix")}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
{setting.injectWebfix && (
|
||||||
|
<Box>
|
||||||
|
{loading ? (
|
||||||
|
<center>
|
||||||
|
<CircularProgress size={16} />
|
||||||
|
</center>
|
||||||
|
) : (
|
||||||
|
sites.map((site) => (
|
||||||
|
<ApiAccordion key={site.pattern} site={site} />
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -15,9 +15,13 @@ 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";
|
||||||
|
import Apis from "./Apis";
|
||||||
|
import Webfix from "./Webfix";
|
||||||
|
|
||||||
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 +30,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 +53,7 @@ export default function Options() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (++i > 8) {
|
if (++i > 8) {
|
||||||
setError(true);
|
setError("Time out.");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,6 +70,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}
|
||||||
@@ -106,7 +126,9 @@ export default function Options() {
|
|||||||
<Route path="/" element={<Layout />}>
|
<Route path="/" element={<Layout />}>
|
||||||
<Route index element={<Setting />} />
|
<Route index element={<Setting />} />
|
||||||
<Route path="rules" element={<Rules />} />
|
<Route path="rules" element={<Rules />} />
|
||||||
|
<Route path="apis" element={<Apis />} />
|
||||||
<Route path="sync" element={<SyncSetting />} />
|
<Route path="sync" element={<SyncSetting />} />
|
||||||
|
<Route path="webfix" element={<Webfix />} />
|
||||||
<Route path="about" element={<About />} />
|
<Route path="about" element={<About />} />
|
||||||
</Route>
|
</Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
|
|||||||
42
src/views/Popup/Header.js
Normal file
42
src/views/Popup/Header.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import Box from "@mui/material/Box";
|
||||||
|
import IconButton from "@mui/material/IconButton";
|
||||||
|
import CloseIcon from "@mui/icons-material/Close";
|
||||||
|
import HomeIcon from "@mui/icons-material/Home";
|
||||||
|
import Stack from "@mui/material/Stack";
|
||||||
|
import DarkModeButton from "../Options/DarkModeButton";
|
||||||
|
|
||||||
|
export default function Header({ setShowPopup }) {
|
||||||
|
const handleHomepage = () => {
|
||||||
|
window.open(process.env.REACT_APP_HOMEPAGE, "_blank");
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack
|
||||||
|
direction="row"
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
spacing={2}
|
||||||
|
>
|
||||||
|
<Stack direction="row" justifyContent="flex-start" alignItems="center">
|
||||||
|
<IconButton onClick={handleHomepage}>
|
||||||
|
<HomeIcon />
|
||||||
|
</IconButton>
|
||||||
|
<Box>
|
||||||
|
{`${process.env.REACT_APP_NAME} v${process.env.REACT_APP_VERSION}`}
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
{setShowPopup ? (
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
setShowPopup(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CloseIcon />
|
||||||
|
</IconButton>
|
||||||
|
) : (
|
||||||
|
<DarkModeButton />
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -10,6 +10,8 @@ import { browser } from "../../libs/browser";
|
|||||||
import { isExt } from "../../libs/client";
|
import { isExt } from "../../libs/client";
|
||||||
import { useI18n } from "../../hooks/I18n";
|
import { useI18n } from "../../hooks/I18n";
|
||||||
import TextField from "@mui/material/TextField";
|
import TextField from "@mui/material/TextField";
|
||||||
|
import Divider from "@mui/material/Divider";
|
||||||
|
import Header from "./Header";
|
||||||
import {
|
import {
|
||||||
MSG_TRANS_TOGGLE,
|
MSG_TRANS_TOGGLE,
|
||||||
MSG_TRANS_GETRULE,
|
MSG_TRANS_GETRULE,
|
||||||
@@ -19,6 +21,7 @@ import {
|
|||||||
OPT_LANGS_TO,
|
OPT_LANGS_TO,
|
||||||
OPT_STYLE_ALL,
|
OPT_STYLE_ALL,
|
||||||
OPT_STYLE_USE_COLOR,
|
OPT_STYLE_USE_COLOR,
|
||||||
|
CACHE_NAME,
|
||||||
} from "../../config";
|
} from "../../config";
|
||||||
import { sendIframeMsg } from "../../libs/iframe";
|
import { sendIframeMsg } from "../../libs/iframe";
|
||||||
|
|
||||||
@@ -66,6 +69,14 @@ export default function Popup({ setShowPopup, translator: tran }) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleClearCache = () => {
|
||||||
|
try {
|
||||||
|
caches.delete(CACHE_NAME);
|
||||||
|
} catch (err) {
|
||||||
|
console.log("[clear cache]", err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isExt) {
|
if (!isExt) {
|
||||||
return;
|
return;
|
||||||
@@ -84,8 +95,14 @@ export default function Popup({ setShowPopup, translator: tran }) {
|
|||||||
|
|
||||||
if (!rule) {
|
if (!rule) {
|
||||||
return (
|
return (
|
||||||
<Box minWidth={300} sx={{ p: 2 }}>
|
<Box minWidth={300}>
|
||||||
<Stack spacing={3}>
|
{isExt && (
|
||||||
|
<>
|
||||||
|
<Header />
|
||||||
|
<Divider />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<Stack sx={{ p: 2 }} spacing={3}>
|
||||||
<Button variant="text" onClick={handleOpenSetting}>
|
<Button variant="text" onClick={handleOpenSetting}>
|
||||||
{i18n("setting")}
|
{i18n("setting")}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -97,17 +114,35 @@ export default function Popup({ setShowPopup, translator: tran }) {
|
|||||||
const { transOpen, translator, fromLang, toLang, textStyle, bgColor } = rule;
|
const { transOpen, translator, fromLang, toLang, textStyle, bgColor } = rule;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box minWidth={300} sx={{ p: 2 }}>
|
<Box minWidth={300}>
|
||||||
<Stack spacing={2}>
|
{isExt && (
|
||||||
<FormControlLabel
|
<>
|
||||||
control={
|
<Header />
|
||||||
<Switch
|
<Divider />
|
||||||
checked={transOpen === "true"}
|
</>
|
||||||
onChange={handleTransToggle}
|
)}
|
||||||
/>
|
<Stack sx={{ p: 2 }} spacing={2}>
|
||||||
}
|
<Stack
|
||||||
label={i18n("translate_alt")}
|
direction="row"
|
||||||
/>
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
spacing={2}
|
||||||
|
>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Switch
|
||||||
|
checked={transOpen === "true"}
|
||||||
|
onChange={handleTransToggle}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={i18n("translate_alt")}
|
||||||
|
/>
|
||||||
|
{!isExt && (
|
||||||
|
<Button variant="text" onClick={handleClearCache}>
|
||||||
|
{i18n("clear_cache")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
select
|
select
|
||||||
|
|||||||
Reference in New Issue
Block a user