Compare commits
168 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63f989b31a | ||
|
|
2ae5d01d5c | ||
|
|
130f1deed1 | ||
|
|
5880d85b48 | ||
|
|
9455670e80 | ||
|
|
e369321c66 | ||
|
|
efc51b0d46 | ||
|
|
d6f3b23b88 | ||
|
|
0a4fa7b9f8 | ||
|
|
2b3e4a8d25 | ||
|
|
bf3a16f96d | ||
|
|
b416e72820 | ||
|
|
ca84bdb227 | ||
|
|
148a4e97a6 | ||
|
|
a13493ebc2 | ||
|
|
ce4ac79e5f | ||
|
|
8f76ea49e7 | ||
|
|
923d3293cd | ||
|
|
7379ff8d15 | ||
|
|
18ebec350d | ||
|
|
3b0cbc53aa | ||
|
|
f00e8ffa4d | ||
|
|
d6f7aad1c3 | ||
|
|
092ea6e836 | ||
|
|
d565e2464a | ||
|
|
2f5d875c47 | ||
|
|
fdb2ddc5f7 | ||
|
|
7a12c5315a | ||
|
|
60d788288d | ||
|
|
dc3c510d57 | ||
|
|
ec6a49f01e | ||
|
|
2b9bfbc20d | ||
|
|
06a51df834 | ||
|
|
6fa183dc56 | ||
|
|
b3cb4049ed | ||
|
|
602b51b1f5 | ||
|
|
a83039577c | ||
|
|
1c77a289a6 | ||
|
|
6278b9124d | ||
|
|
f94cafdbcc | ||
|
|
e13da2caba | ||
|
|
d833fa8dfd | ||
|
|
c921cc59b9 | ||
|
|
7a2c594324 | ||
|
|
0eeb9c2fbf | ||
|
|
ac921cd5a0 | ||
|
|
3ea14c1687 | ||
|
|
6e927473b9 | ||
|
|
1d9e9c1b7d | ||
|
|
54a6189b0c | ||
|
|
85aa9f39c2 | ||
|
|
bda83ce76e | ||
|
|
9ee4c20250 | ||
|
|
fbc70e43e3 | ||
|
|
bc4b4a2171 | ||
|
|
96f9bf6f6f | ||
|
|
9f0986536a | ||
|
|
f668aa7acd | ||
|
|
fc50f4784a | ||
|
|
1fa58cad31 | ||
|
|
469e62557c | ||
|
|
75830aaea7 | ||
|
|
61ef5df559 | ||
|
|
14b5ba9c4c | ||
|
|
9e9c56a3b4 | ||
|
|
3a79f55614 | ||
|
|
af25ee5c11 | ||
|
|
6dd581d5e2 | ||
|
|
746ec019c4 | ||
|
|
2b70f28b0b | ||
|
|
45127646e8 | ||
|
|
83e9c1dd97 | ||
|
|
2eabb7d5ac | ||
|
|
9d4c596b4b | ||
|
|
cc38ab6c45 | ||
|
|
586fa09c7b | ||
|
|
0c45bc5ea8 | ||
|
|
9d9c0633f0 | ||
|
|
47f9635b10 | ||
|
|
68088f5e17 | ||
|
|
77a37cc6df | ||
|
|
420e59bf81 | ||
|
|
dbc5135301 | ||
|
|
8c7d6bb552 | ||
|
|
2b5c1952c0 | ||
|
|
85a82618e5 | ||
|
|
0280ac34c3 | ||
|
|
439900154b | ||
|
|
4a6e902684 | ||
|
|
71bbd2e54a | ||
|
|
3083d8e147 | ||
|
|
e74883e9c2 | ||
|
|
0816a9d167 | ||
|
|
4b3e91fa84 | ||
|
|
0973a0b60e | ||
|
|
de5f61126d | ||
|
|
0c20ca761f | ||
|
|
4bce56207e | ||
|
|
dca54e0033 | ||
|
|
309646bf1d | ||
|
|
18b9961b39 | ||
|
|
1e51ff17f2 | ||
|
|
63b5f707e2 | ||
|
|
30efb6ee7a | ||
|
|
61b017618a | ||
|
|
1e0397adc9 | ||
|
|
48b34bf95f | ||
|
|
d5fc69e210 | ||
|
|
59f9dd697f | ||
|
|
c9d72323f1 | ||
|
|
e87f7f3abe | ||
|
|
82ebbcb6d6 | ||
|
|
2db11070c5 | ||
|
|
5efd2517e7 | ||
|
|
c0ba654678 | ||
|
|
546a5a549b | ||
|
|
cbf02c34e3 | ||
|
|
74a7258f10 | ||
|
|
1006c044bc | ||
|
|
ef4ea719f3 | ||
|
|
8b34afe69f | ||
|
|
01292af298 | ||
|
|
cff8b2fe39 | ||
|
|
2cb20b5cc0 | ||
|
|
8f2aed18fe | ||
|
|
d85831cc9a | ||
|
|
55dc3a5556 | ||
|
|
591afe08bd | ||
|
|
748f2002ab | ||
|
|
d2d18a2384 | ||
|
|
35f4fa6aa7 | ||
|
|
66fc2d22ed | ||
|
|
16cf9ee1ed | ||
|
|
d9d97bf14c | ||
|
|
dc811bd3c7 | ||
|
|
b939d1849a | ||
|
|
beca31f55d | ||
|
|
c7df103950 | ||
|
|
4bf7972ad5 | ||
|
|
534eaed1ed | ||
|
|
7e014e7385 | ||
|
|
34adb2660b | ||
|
|
b6bc165cf0 | ||
|
|
bdd5ed7fc7 | ||
|
|
95d19417c3 | ||
|
|
30ebebdd71 | ||
|
|
e9c557776d | ||
|
|
535a43b698 | ||
|
|
59752ed4aa | ||
|
|
b3e7b8f3f1 | ||
|
|
c4e9365512 | ||
|
|
7d3972d3a8 | ||
|
|
52ca4306fd | ||
|
|
da368ee612 | ||
|
|
22c50e7765 | ||
|
|
7bc39dd1bc | ||
|
|
c80ead6116 | ||
|
|
67e76e4009 | ||
|
|
b213218a30 | ||
|
|
c629a1252c | ||
|
|
64d2481e93 | ||
|
|
e7d6a6add8 | ||
|
|
edc25f7da4 | ||
|
|
5bff84ace1 | ||
|
|
f8bfcba317 | ||
|
|
013a05201b | ||
|
|
433e811821 | ||
|
|
df4cfc0fbc |
12
.env
12
.env
@@ -2,28 +2,18 @@ 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.7.12
|
REACT_APP_VERSION=1.8.7
|
||||||
|
|
||||||
REACT_APP_HOMEPAGE=https://github.com/fishjar/kiss-translator
|
REACT_APP_HOMEPAGE=https://github.com/fishjar/kiss-translator
|
||||||
|
|
||||||
REACT_APP_OPTIONSPAGE=https://fishjar.github.io/kiss-translator/options.html
|
REACT_APP_OPTIONSPAGE=https://fishjar.github.io/kiss-translator/options.html
|
||||||
REACT_APP_OPTIONSPAGE2=https://kiss-translator.rayjar.com/options
|
|
||||||
REACT_APP_OPTIONSPAGE_DEV=http://localhost:3000/options.html
|
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_RULESURL=https://fishjar.github.io/kiss-rules/kiss-rules.json
|
REACT_APP_RULESURL=https://fishjar.github.io/kiss-rules/kiss-rules.json
|
||||||
REACT_APP_RULESURL_ON=https://fishjar.github.io/kiss-rules/kiss-rules-on.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_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_VERSIONFILE2=https://kiss-translator.rayjar.com/version.txt
|
|
||||||
|
|
||||||
REACT_APP_USERSCRIPT_DOWNLOADURL=https://fishjar.github.io/kiss-translator/kiss-translator.user.js
|
REACT_APP_USERSCRIPT_DOWNLOADURL=https://fishjar.github.io/kiss-translator/kiss-translator.user.js
|
||||||
REACT_APP_USERSCRIPT_DOWNLOADURL2=https://kiss-translator.rayjar.com/kiss-translator.user.js
|
|
||||||
|
|
||||||
REACT_APP_USERSCRIPT_IOS_DOWNLOADURL=https://fishjar.github.io/kiss-translator/kiss-translator-ios-safari.user.js
|
REACT_APP_USERSCRIPT_IOS_DOWNLOADURL=https://fishjar.github.io/kiss-translator/kiss-translator-ios-safari.user.js
|
||||||
REACT_APP_USERSCRIPT_IOS_DOWNLOADURL2=https://kiss-translator.rayjar.com/kiss-translator-ios-safari.user.js
|
|
||||||
|
|||||||
24
README.en.md
24
README.en.md
@@ -1,6 +1,6 @@
|
|||||||
# KISS Translator
|
# KISS Translator
|
||||||
|
|
||||||
A simple [bilingual translation extension & Greasemonkey script](https://github.com/fishjar/kiss-translator).
|
A simple, open source [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)
|
||||||
|
|
||||||
@@ -9,10 +9,10 @@ A simple [bilingual translation extension & Greasemonkey script](https://github.
|
|||||||
- [x] Keep it simple, smart
|
- [x] Keep it simple, smart
|
||||||
- [x] Open source
|
- [x] Open source
|
||||||
- [x] Adapt to common browsers
|
- [x] Adapt to common browsers
|
||||||
- [x] Chrome/Edge/Firefox/Kiwi
|
- [x] Chrome/Edge/Firefox/Kiwi/Orion
|
||||||
- [ ] Safari
|
- [ ] Safari
|
||||||
- [x] Supports multiple translation services
|
- [x] Supports multiple translation services
|
||||||
- [x] Google/Microsoft/DeepL/OpenAI/CloudflareAI/Baidu/Tencent
|
- [x] Google/Microsoft/DeepL/OpenAI/Gemini/CloudflareAI/Baidu/Tencent
|
||||||
- [x] Custom translation interface
|
- [x] Custom translation interface
|
||||||
- [x] Covers common translation scenarios
|
- [x] Covers common translation scenarios
|
||||||
- [x] Web bilingual translation
|
- [x] Web bilingual translation
|
||||||
@@ -26,12 +26,13 @@ A simple [bilingual translation extension & Greasemonkey script](https://github.
|
|||||||
- [x] WebDAV
|
- [x] WebDAV
|
||||||
- [x] Custom translation rules
|
- [x] Custom translation rules
|
||||||
- [x] Rule subscription/rule sharing
|
- [x] Rule subscription/rule sharing
|
||||||
|
- [x] Customized terminology
|
||||||
- [x] Custom translation style
|
- [x] Custom translation style
|
||||||
- [x] Custom shortcut keys
|
- [x] Custom shortcut keys
|
||||||
- `Alt+Q` Toggle Translation
|
- `Alt+Q` Toggle Translation
|
||||||
- `Alt+C` Toggle Styles
|
- `Alt+C` Toggle Styles
|
||||||
- `Alt+K` Open Setting Popup
|
- `Alt+K` Open Setting Popup
|
||||||
- `Alt+B` Open Translate Popup
|
- `Alt+S` Open Translate Popup / Translate Selected Text
|
||||||
- `Alt+O` Open Options Page
|
- `Alt+O` Open Options Page
|
||||||
- `Alt+I` Input Box Translation
|
- `Alt+I` Input Box Translation
|
||||||
|
|
||||||
@@ -39,17 +40,19 @@ A simple [bilingual translation extension & Greasemonkey script](https://github.
|
|||||||
|
|
||||||
> Note: For the following reasons, it is recommended to use browser extensions first
|
> Note: For the following reasons, it is recommended to use browser extensions first
|
||||||
>
|
>
|
||||||
> - Browser extension can use local language recognition
|
> - Browser extensions have more complete functions (local language recognition, context menu, etc.)
|
||||||
> - Grease Monkey script will encounter more usage problems
|
> - Grease Monkey script will encounter more usage problems (cross domain issues, script conflicts, etc.)
|
||||||
|
|
||||||
- [x] Browser extension
|
- [x] Browser extension
|
||||||
- [x] Chrome [Installation address](https://chrome.google.com/webstore/detail/kiss-translator/bdiifdefkgmcblbcghdlonllpjhhjgof?hl=zh-CN)
|
- [x] Chrome [Installation address](https://chrome.google.com/webstore/detail/kiss-translator/bdiifdefkgmcblbcghdlonllpjhhjgof?hl=zh-CN)
|
||||||
|
- [x] Kiwi (Android)
|
||||||
|
- [x] Orion (iOS)
|
||||||
- [x] Edge [Installation address](https://microsoftedge.microsoft.com/addons/detail/%E7%AE%80%E7%BA%A6%E7%BF%BB%E8%AF%91/jemckldkclkinpjighnoilpbldbdmmlh?hl=zh-CN)
|
- [x] Edge [Installation address](https://microsoftedge.microsoft.com/addons/detail/%E7%AE%80%E7%BA%A6%E7%BF%BB%E8%AF%91/jemckldkclkinpjighnoilpbldbdmmlh?hl=zh-CN)
|
||||||
- [x] Firefox [Installation address](https://addons.mozilla.org/zh-CN/firefox/addon/kiss-translator/)
|
- [x] Firefox [Installation address](https://addons.mozilla.org/zh-CN/firefox/addon/kiss-translator/)
|
||||||
- [ ] Safari
|
- [ ] Safari
|
||||||
- [x] GreaseMonkey Script
|
- [x] GreaseMonkey Script
|
||||||
- [x] Chrome/Edge/Firefox ([Tampermonkey](https://www.tampermonkey.net/)/[Violentmonkey](https://violentmonkey.github.io/)) [Installation link](https://fishjar.github.io/kiss-translator/kiss-translator.user.js)
|
- [x] Chrome/Edge/Firefox ([Tampermonkey](https://www.tampermonkey.net/)/[Violentmonkey](https://violentmonkey.github.io/)) [Installation link](https://fishjar.github.io/kiss-translator/kiss-translator.user.js)
|
||||||
- Greasy Fork [Installation address](https://greasyfork.org/zh-CN/scripts/472840-kiss-translator)
|
- [Greasy Fork](https://greasyfork.org/zh-CN/scripts/472840-kiss-translator)
|
||||||
- [x] iOS Safari ([Userscripts Safari](https://github.com/quoid/userscripts)) [Installation link](https://fishjar.github.io/kiss-translator/kiss-translator-ios-safari.user.js)
|
- [x] iOS Safari ([Userscripts Safari](https://github.com/quoid/userscripts)) [Installation link](https://fishjar.github.io/kiss-translator/kiss-translator-ios-safari.user.js)
|
||||||
|
|
||||||
## Associated Projects
|
## Associated Projects
|
||||||
@@ -61,9 +64,6 @@ A simple [bilingual translation extension & Greasemonkey script](https://github.
|
|||||||
- Community subscription rules: [https://github.com/fishjar/kiss-rules](https://github.com/fishjar/kiss-rules)
|
- 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.
|
- Provides the latest and most complete list of subscription rules maintained by the community.
|
||||||
- Help with rules-related issues.
|
- 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)
|
- 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.
|
- If you encounter network problems when accessing a certain translation interface, this proxy service may help you.
|
||||||
- Deploy and manage by yourself.
|
- Deploy and manage by yourself.
|
||||||
@@ -84,3 +84,7 @@ pnpm build
|
|||||||
## Discussion
|
## Discussion
|
||||||
|
|
||||||
- Join [Telegram Group](https://t.me/+RRCu_4oNwrM2NmFl)
|
- Join [Telegram Group](https://t.me/+RRCu_4oNwrM2NmFl)
|
||||||
|
|
||||||
|
## Appreciate
|
||||||
|
|
||||||
|

|
||||||
|
|||||||
26
README.md
26
README.md
@@ -1,6 +1,6 @@
|
|||||||
# 简约翻译
|
# 简约翻译
|
||||||
|
|
||||||
一个简约的 [双语对照翻译扩展 & 油猴脚本](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)
|
||||||
|
|
||||||
@@ -9,10 +9,10 @@
|
|||||||
- [x] 保持简约
|
- [x] 保持简约
|
||||||
- [x] 开放源代码
|
- [x] 开放源代码
|
||||||
- [x] 适配常见浏览器
|
- [x] 适配常见浏览器
|
||||||
- [x] Chrome/Edge/Firefox/Kiwi
|
- [x] Chrome/Edge/Firefox/Kiwi/Orion
|
||||||
- [ ] Safari
|
- [ ] Safari
|
||||||
- [x] 支持多种翻译服务
|
- [x] 支持多种翻译服务
|
||||||
- [x] Google/Microsoft/DeepL/OpenAI/CloudflareAI/Baidu/Tencent
|
- [x] Google/Microsoft/DeepL/OpenAI/Gemini/CloudflareAI/Baidu/Tencent
|
||||||
- [x] 自定义翻译接口
|
- [x] 自定义翻译接口
|
||||||
- [x] 覆盖常见翻译场景
|
- [x] 覆盖常见翻译场景
|
||||||
- [x] 网页双语对照翻译
|
- [x] 网页双语对照翻译
|
||||||
@@ -26,12 +26,13 @@
|
|||||||
- [x] WebDAV
|
- [x] WebDAV
|
||||||
- [x] 自定义翻译规则
|
- [x] 自定义翻译规则
|
||||||
- [x] 规则订阅/规则分享
|
- [x] 规则订阅/规则分享
|
||||||
|
- [x] 自定义专业术语
|
||||||
- [x] 自定义译文样式
|
- [x] 自定义译文样式
|
||||||
- [x] 自定义快捷键
|
- [x] 自定义快捷键
|
||||||
- `Alt+Q` 开启翻译
|
- `Alt+Q` 开启翻译
|
||||||
- `Alt+C` 切换样式
|
- `Alt+C` 切换样式
|
||||||
- `Alt+K` 打开设置弹窗
|
- `Alt+K` 打开设置弹窗
|
||||||
- `Alt+B` 打开翻译弹窗
|
- `Alt+S` 打开翻译弹窗/翻译选中文字
|
||||||
- `Alt+O` 打开设置页面
|
- `Alt+O` 打开设置页面
|
||||||
- `Alt+I` 输入框翻译
|
- `Alt+I` 输入框翻译
|
||||||
|
|
||||||
@@ -39,17 +40,19 @@
|
|||||||
|
|
||||||
> 注:基于以下原因,建议优先使用浏览器扩展
|
> 注:基于以下原因,建议优先使用浏览器扩展
|
||||||
>
|
>
|
||||||
> - 浏览器扩展可以使用本地的语言识别
|
> - 浏览器扩展的功能更完整(本地语言识别、右键菜单等)
|
||||||
> - 油猴脚本会遇到更多使用上的问题
|
> - 油猴脚本会遇到更多使用上的问题(跨域问题、脚本冲突等)
|
||||||
|
|
||||||
- [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] Kiwi (Android)
|
||||||
|
- [x] Orion (iOS)
|
||||||
- [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)
|
||||||
- [x] Firefox [安装地址](https://addons.mozilla.org/zh-CN/firefox/addon/kiss-translator/)
|
- [x] Firefox [安装地址](https://addons.mozilla.org/zh-CN/firefox/addon/kiss-translator/)
|
||||||
- [ ] Safari
|
- [ ] Safari
|
||||||
- [x] 油猴脚本
|
- [x] 油猴脚本
|
||||||
- [x] Chrome/Edge/Firefox ([Tampermonkey](https://www.tampermonkey.net/)/[Violentmonkey](https://violentmonkey.github.io/)) [安装链接](https://fishjar.github.io/kiss-translator/kiss-translator.user.js)
|
- [x] Chrome/Edge/Firefox ([Tampermonkey](https://www.tampermonkey.net/)/[Violentmonkey](https://violentmonkey.github.io/)) [安装链接](https://fishjar.github.io/kiss-translator/kiss-translator.user.js)
|
||||||
- Greasy Fork [安装地址](https://greasyfork.org/zh-CN/scripts/472840-kiss-translator)
|
- [Greasy Fork](https://greasyfork.org/zh-CN/scripts/472840-kiss-translator)
|
||||||
- [x] iOS Safari ([Userscripts Safari](https://github.com/quoid/userscripts)) [安装链接](https://fishjar.github.io/kiss-translator/kiss-translator-ios-safari.user.js)
|
- [x] iOS Safari ([Userscripts Safari](https://github.com/quoid/userscripts)) [安装链接](https://fishjar.github.io/kiss-translator/kiss-translator-ios-safari.user.js)
|
||||||
|
|
||||||
## 关联项目
|
## 关联项目
|
||||||
@@ -61,11 +64,8 @@
|
|||||||
- 社区订阅规则: [https://github.com/fishjar/kiss-rules](https://github.com/fishjar/kiss-rules)
|
- 社区订阅规则: [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-proxy](https://github.com/fishjar/kiss-proxy)
|
||||||
- 如果访问某个翻译接口遇到网络问题,这个代理服务也许可以帮你到你。
|
- 如果访问某个翻译接口遇到网络问题,这个代理服务也许可以帮到你。
|
||||||
- 自己部署,自己管理。
|
- 自己部署,自己管理。
|
||||||
- 简约词典插件: [https://github.com/fishjar/kiss-dictionary](https://github.com/fishjar/kiss-dictionary)
|
- 简约词典插件: [https://github.com/fishjar/kiss-dictionary](https://github.com/fishjar/kiss-dictionary)
|
||||||
- 搭配本项目一起使用的划词翻译插件。
|
- 搭配本项目一起使用的划词翻译插件。
|
||||||
@@ -84,3 +84,7 @@ pnpm build
|
|||||||
## 交流
|
## 交流
|
||||||
|
|
||||||
- 加入 [Telegram 群](https://t.me/+RRCu_4oNwrM2NmFl)
|
- 加入 [Telegram 群](https://t.me/+RRCu_4oNwrM2NmFl)
|
||||||
|
|
||||||
|
## 赞赏
|
||||||
|
|
||||||
|

|
||||||
|
|||||||
@@ -85,6 +85,7 @@ const userscriptWebpack = (config, env) => {
|
|||||||
// @updateURL ${process.env.REACT_APP_USERSCRIPT_DOWNLOADURL}
|
// @updateURL ${process.env.REACT_APP_USERSCRIPT_DOWNLOADURL}
|
||||||
// @grant GM.xmlHttpRequest
|
// @grant GM.xmlHttpRequest
|
||||||
// @grant GM.registerMenuCommand
|
// @grant GM.registerMenuCommand
|
||||||
|
// @grant GM.unregisterMenuCommand
|
||||||
// @grant GM.setValue
|
// @grant GM.setValue
|
||||||
// @grant GM.getValue
|
// @grant GM.getValue
|
||||||
// @grant GM.deleteValue
|
// @grant GM.deleteValue
|
||||||
@@ -97,6 +98,7 @@ const userscriptWebpack = (config, env) => {
|
|||||||
// @connect api.deepl.com
|
// @connect api.deepl.com
|
||||||
// @connect www2.deepl.com
|
// @connect www2.deepl.com
|
||||||
// @connect api.openai.com
|
// @connect api.openai.com
|
||||||
|
// @connect generativelanguage.googleapis.com
|
||||||
// @connect openai.azure.com
|
// @connect openai.azure.com
|
||||||
// @connect workers.dev
|
// @connect workers.dev
|
||||||
// @connect github.io
|
// @connect github.io
|
||||||
|
|||||||
22
package.json
22
package.json
@@ -1,20 +1,21 @@
|
|||||||
{
|
{
|
||||||
"name": "kiss-translator",
|
"name": "kiss-translator",
|
||||||
"description": "A minimalist bilingual translation Extension & Greasemonkey Script",
|
"description": "A minimalist bilingual translation Extension & Greasemonkey Script",
|
||||||
"version": "1.7.12",
|
"version": "1.8.7",
|
||||||
"author": "Gabe<yugang2002@gmail.com>",
|
"author": "Gabe<yugang2002@gmail.com>",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/cache": "^11.11.0",
|
"@emotion/cache": "^11.11.0",
|
||||||
"@emotion/react": "^11.11.1",
|
"@emotion/react": "^11.11.1",
|
||||||
"@emotion/styled": "^11.10.8",
|
"@emotion/styled": "^11.11.0",
|
||||||
"@mui/icons-material": "^5.11.11",
|
"@mui/icons-material": "^5.15.15",
|
||||||
"@mui/material": "^5.11.12",
|
"@mui/lab": "5.0.0-alpha.170",
|
||||||
|
"@mui/material": "^5.15.15",
|
||||||
"query-string": "^8.1.0",
|
"query-string": "^8.1.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-markdown": "^8.0.7",
|
"react-markdown": "^8.0.7",
|
||||||
"react-router-dom": "^6.10.0",
|
"react-router-dom": "^6.16.0",
|
||||||
"react-scripts": "5.0.1",
|
"react-scripts": "5.0.1",
|
||||||
"webdav": "^5.3.0",
|
"webdav": "^5.3.0",
|
||||||
"webextension-polyfill": "^0.10.0"
|
"webextension-polyfill": "^0.10.0"
|
||||||
@@ -22,14 +23,15 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "REACT_APP_CLIENT=web react-app-rewired start",
|
"start": "REACT_APP_CLIENT=web react-app-rewired start",
|
||||||
"start:userscript": "REACT_APP_CLIENT=userscript react-app-rewired start",
|
"start:userscript": "REACT_APP_CLIENT=userscript react-app-rewired start",
|
||||||
"build:chrome": "rm -rf build/chrome && BUILD_PATH=./build/chrome REACT_APP_CLIENT=chrome react-app-rewired build",
|
"build:chrome": "rm -rf build/chrome && BUILD_PATH=./build/chrome REACT_APP_CLIENT=chrome react-app-rewired build && rm build/chrome/content.html",
|
||||||
"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 && rm build/*/manifest.firefox.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-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/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": "rm -rf build/userscript && mkdir build/userscript && cp build/web/*.user.js build/userscript/",
|
"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": "pnpm build:chrome && pnpm build:edge && pnpm build:firefox && pnpm build:web && pnpm build:userscript-ios && pnpm build:userscript && pnpm build:rules",
|
"build": "pnpm build:chrome && pnpm build:edge && pnpm build:firefox && pnpm build:web && pnpm build:userscript-ios && pnpm build:userscript && pnpm build:rules",
|
||||||
|
"pack": "cd build && zip -r chrome.zip chrome && zip -r edge.zip edge && cd firefox && zip -r ../firefox.zip .",
|
||||||
"test": "react-app-rewired test",
|
"test": "react-app-rewired test",
|
||||||
"eject": "react-scripts eject"
|
"eject": "react-scripts eject"
|
||||||
},
|
},
|
||||||
@@ -57,10 +59,10 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.22.10",
|
"@babel/core": "^7.22.20",
|
||||||
"@babel/node": "^7.22.10",
|
"@babel/node": "^7.22.19",
|
||||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||||
"@babel/preset-env": "^7.22.10",
|
"@babel/preset-env": "^7.22.20",
|
||||||
"react-app-rewired": "^2.2.1"
|
"react-app-rewired": "^2.2.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
673
pnpm-lock.yaml
generated
673
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -13,5 +13,8 @@
|
|||||||
},
|
},
|
||||||
"open_options": {
|
"open_options": {
|
||||||
"message": "Open Options"
|
"message": "Open Options"
|
||||||
|
},
|
||||||
|
"open_tranbox": {
|
||||||
|
"message": "Translate Popup/Selected"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,5 +13,8 @@
|
|||||||
},
|
},
|
||||||
"open_options": {
|
"open_options": {
|
||||||
"message": "打开设置"
|
"message": "打开设置"
|
||||||
|
},
|
||||||
|
"open_tranbox": {
|
||||||
|
"message": "翻译弹窗/选中文字"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,6 +64,45 @@
|
|||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
<div id="root">
|
<div id="root">
|
||||||
|
<p>You need to enable <code>JavaScript</code> to run <span>this app.</span></p>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
<div id="content">
|
<div id="content">
|
||||||
<p>You need to enable JavaScript to run <span>this app.</span></p>
|
<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
|
The <span>embargo</span> has just lifted to confirm that AmpereOne is
|
||||||
|
|||||||
@@ -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.7.12",
|
"version": "1.8.7",
|
||||||
"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",
|
||||||
@@ -28,6 +28,12 @@
|
|||||||
},
|
},
|
||||||
"description": "__MSG_toggle_translate__"
|
"description": "__MSG_toggle_translate__"
|
||||||
},
|
},
|
||||||
|
"openTranbox": {
|
||||||
|
"suggested_key": {
|
||||||
|
"default": "Alt+S"
|
||||||
|
},
|
||||||
|
"description": "__MSG_open_tranbox__"
|
||||||
|
},
|
||||||
"toggleStyle": {
|
"toggleStyle": {
|
||||||
"suggested_key": {
|
"suggested_key": {
|
||||||
"default": "Alt+C"
|
"default": "Alt+C"
|
||||||
@@ -35,13 +41,16 @@
|
|||||||
"description": "__MSG_toggle_style__"
|
"description": "__MSG_toggle_style__"
|
||||||
},
|
},
|
||||||
"openOptions": {
|
"openOptions": {
|
||||||
"suggested_key": {
|
|
||||||
"default": "Alt+O"
|
|
||||||
},
|
|
||||||
"description": "__MSG_open_options__"
|
"description": "__MSG_open_options__"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"permissions": ["<all_urls>", "storage"],
|
"permissions": [
|
||||||
|
"<all_urls>",
|
||||||
|
"storage",
|
||||||
|
"contextMenus",
|
||||||
|
"scripting",
|
||||||
|
"declarativeNetRequest"
|
||||||
|
],
|
||||||
"icons": {
|
"icons": {
|
||||||
"16": "images/logo16.png",
|
"16": "images/logo16.png",
|
||||||
"32": "images/logo32.png",
|
"32": "images/logo32.png",
|
||||||
|
|||||||
@@ -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.7.12",
|
"version": "1.8.7",
|
||||||
"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",
|
||||||
@@ -29,6 +29,12 @@
|
|||||||
},
|
},
|
||||||
"description": "__MSG_toggle_translate__"
|
"description": "__MSG_toggle_translate__"
|
||||||
},
|
},
|
||||||
|
"openTranbox": {
|
||||||
|
"suggested_key": {
|
||||||
|
"default": "Alt+S"
|
||||||
|
},
|
||||||
|
"description": "__MSG_open_tranbox__"
|
||||||
|
},
|
||||||
"toggleStyle": {
|
"toggleStyle": {
|
||||||
"suggested_key": {
|
"suggested_key": {
|
||||||
"default": "Alt+C"
|
"default": "Alt+C"
|
||||||
@@ -36,13 +42,15 @@
|
|||||||
"description": "__MSG_toggle_style__"
|
"description": "__MSG_toggle_style__"
|
||||||
},
|
},
|
||||||
"openOptions": {
|
"openOptions": {
|
||||||
"suggested_key": {
|
|
||||||
"default": "Alt+O"
|
|
||||||
},
|
|
||||||
"description": "__MSG_open_options__"
|
"description": "__MSG_open_options__"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"permissions": ["storage"],
|
"permissions": [
|
||||||
|
"storage",
|
||||||
|
"contextMenus",
|
||||||
|
"scripting",
|
||||||
|
"declarativeNetRequest"
|
||||||
|
],
|
||||||
"host_permissions": ["<all_urls>"],
|
"host_permissions": ["<all_urls>"],
|
||||||
"icons": {
|
"icons": {
|
||||||
"16": "images/logo16.png",
|
"16": "images/logo16.png",
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import { getBdauth, setBdauth } from "../libs/storage";
|
import { getBdauth, setBdauth } from "../libs/storage";
|
||||||
import { URL_BAIDU_WEB, URL_BAIDU_TRAN } from "../config";
|
import {
|
||||||
|
URL_BAIDU_WEB,
|
||||||
|
URL_BAIDU_TRANSAPI_V2,
|
||||||
|
URL_BAIDU_TRANSAPI,
|
||||||
|
DEFAULT_USER_AGENT,
|
||||||
|
} from "../config";
|
||||||
import { fetchApi } from "../libs/fetch";
|
import { fetchApi } from "../libs/fetch";
|
||||||
|
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
@@ -203,7 +208,12 @@ const _bdAuth = () => {
|
|||||||
|
|
||||||
const bdAuth = _bdAuth();
|
const bdAuth = _bdAuth();
|
||||||
|
|
||||||
export const genBaidu = async ({ text, from, to }) => {
|
/**
|
||||||
|
* 失效作废
|
||||||
|
* @param {*} param0
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const genBaiduV2 = async ({ text, from, to }) => {
|
||||||
const { token, gtk } = await bdAuth();
|
const { token, gtk } = await bdAuth();
|
||||||
const sign = getSign(text, gtk);
|
const sign = getSign(text, gtk);
|
||||||
const data = {
|
const data = {
|
||||||
@@ -217,7 +227,7 @@ export const genBaidu = async ({ text, from, to }) => {
|
|||||||
ts: Date.now(),
|
ts: Date.now(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const input = `${URL_BAIDU_TRAN}?from=${from}&to=${to}`;
|
const input = `${URL_BAIDU_TRANSAPI_V2}?from=${from}&to=${to}`;
|
||||||
const init = {
|
const init = {
|
||||||
headers: {
|
headers: {
|
||||||
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
|
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
|
||||||
@@ -228,3 +238,24 @@ export const genBaidu = async ({ text, from, to }) => {
|
|||||||
|
|
||||||
return [input, init];
|
return [input, init];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const genBaidu = async ({ text, from, to }) => {
|
||||||
|
const data = {
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
query: text,
|
||||||
|
source: "txt",
|
||||||
|
};
|
||||||
|
|
||||||
|
const init = {
|
||||||
|
headers: {
|
||||||
|
// Origin: "https://fanyi.baidu.com",
|
||||||
|
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
|
||||||
|
"User-Agent": DEFAULT_USER_AGENT,
|
||||||
|
},
|
||||||
|
method: "POST",
|
||||||
|
body: queryString.stringify(data),
|
||||||
|
};
|
||||||
|
|
||||||
|
return [URL_BAIDU_TRANSAPI, init];
|
||||||
|
};
|
||||||
|
|||||||
@@ -6,14 +6,22 @@ import {
|
|||||||
OPT_TRANS_DEEPL,
|
OPT_TRANS_DEEPL,
|
||||||
OPT_TRANS_DEEPLFREE,
|
OPT_TRANS_DEEPLFREE,
|
||||||
OPT_TRANS_DEEPLX,
|
OPT_TRANS_DEEPLX,
|
||||||
|
OPT_TRANS_NIUTRANS,
|
||||||
OPT_TRANS_BAIDU,
|
OPT_TRANS_BAIDU,
|
||||||
OPT_TRANS_TENCENT,
|
OPT_TRANS_TENCENT,
|
||||||
OPT_TRANS_OPENAI,
|
OPT_TRANS_OPENAI,
|
||||||
|
OPT_TRANS_GEMINI,
|
||||||
OPT_TRANS_CLOUDFLAREAI,
|
OPT_TRANS_CLOUDFLAREAI,
|
||||||
OPT_TRANS_CUSTOMIZE,
|
OPT_TRANS_CUSTOMIZE,
|
||||||
|
OPT_TRANS_CUSTOMIZE_2,
|
||||||
|
OPT_TRANS_CUSTOMIZE_3,
|
||||||
|
OPT_TRANS_CUSTOMIZE_4,
|
||||||
|
OPT_TRANS_CUSTOMIZE_5,
|
||||||
URL_CACHE_TRAN,
|
URL_CACHE_TRAN,
|
||||||
KV_SALT_SYNC,
|
KV_SALT_SYNC,
|
||||||
URL_BAIDU_LANGDETECT,
|
URL_BAIDU_LANGDETECT,
|
||||||
|
URL_BAIDU_SUGGEST,
|
||||||
|
URL_BAIDU_TTS,
|
||||||
OPT_LANGS_BAIDU,
|
OPT_LANGS_BAIDU,
|
||||||
URL_TENCENT_TRANSMART,
|
URL_TENCENT_TRANSMART,
|
||||||
OPT_LANGS_TENCENT,
|
OPT_LANGS_TENCENT,
|
||||||
@@ -69,6 +77,44 @@ export const apiBaiduLangdetect = async (text) => {
|
|||||||
return "";
|
return "";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 百度翻译建议
|
||||||
|
* @param {*} text
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const apiBaiduSuggest = async (text) => {
|
||||||
|
const res = await fetchPolyfill(URL_BAIDU_SUGGEST, {
|
||||||
|
headers: {
|
||||||
|
"Content-type": "application/json",
|
||||||
|
},
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({
|
||||||
|
kw: text,
|
||||||
|
}),
|
||||||
|
useCache: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.errno === 0) {
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 百度语音
|
||||||
|
* @param {*} text
|
||||||
|
* @param {*} lan
|
||||||
|
* @param {*} spd
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const apiBaiduTTS = (text, lan = "uk", spd = 3) => {
|
||||||
|
const url = `${URL_BAIDU_TTS}?${queryString.stringify({ lan, text, spd })}`;
|
||||||
|
return fetchPolyfill(url, {
|
||||||
|
useCache: false, // 为避免缓存过快增长,禁用缓存语音数据
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 腾讯语言识别
|
* 腾讯语言识别
|
||||||
* @param {*} text
|
* @param {*} text
|
||||||
@@ -124,11 +170,14 @@ export const apiTranslate = async ({
|
|||||||
return [trText, isSame];
|
return [trText, isSame];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 版本号一/二位升级,旧缓存失效
|
||||||
|
const [v1, v2] = process.env.REACT_APP_VERSION.split(".");
|
||||||
const cacheOpts = {
|
const cacheOpts = {
|
||||||
translator,
|
translator,
|
||||||
text,
|
text,
|
||||||
fromLang,
|
fromLang,
|
||||||
toLang,
|
toLang,
|
||||||
|
version: [v1, v2].join("."),
|
||||||
};
|
};
|
||||||
|
|
||||||
const transOpts = {
|
const transOpts = {
|
||||||
@@ -154,7 +203,9 @@ export const apiTranslate = async ({
|
|||||||
isSame = to === res.src;
|
isSame = to === res.src;
|
||||||
break;
|
break;
|
||||||
case OPT_TRANS_MICROSOFT:
|
case OPT_TRANS_MICROSOFT:
|
||||||
trText = res[0].translations.map((item) => item.text).join(" ");
|
trText = res
|
||||||
|
.map((item) => item.translations.map((item) => item.text).join(" "))
|
||||||
|
.join(" ");
|
||||||
isSame = text === trText;
|
isSame = text === trText;
|
||||||
break;
|
break;
|
||||||
case OPT_TRANS_DEEPL:
|
case OPT_TRANS_DEEPL:
|
||||||
@@ -169,16 +220,37 @@ export const apiTranslate = async ({
|
|||||||
trText = res.data;
|
trText = res.data;
|
||||||
isSame = to === res.source_lang;
|
isSame = to === res.source_lang;
|
||||||
break;
|
break;
|
||||||
|
case OPT_TRANS_NIUTRANS:
|
||||||
|
const json = JSON.parse(res);
|
||||||
|
if (json.error_msg) {
|
||||||
|
throw new Error(json.error_msg);
|
||||||
|
}
|
||||||
|
trText = json.tgt_text;
|
||||||
|
isSame = to === json.from;
|
||||||
|
break;
|
||||||
case OPT_TRANS_BAIDU:
|
case OPT_TRANS_BAIDU:
|
||||||
trText = res.trans_result?.data.map((item) => item.dst).join(" ");
|
// trText = res.trans_result?.data.map((item) => item.dst).join(" ");
|
||||||
isSame = res.trans_result?.to === res.trans_result?.from;
|
// isSame = res.trans_result?.to === res.trans_result?.from;
|
||||||
|
if (res.type === 1) {
|
||||||
|
trText = Object.keys(JSON.parse(res.result).content[0].mean[0].cont)[0];
|
||||||
|
isSame = to === res.from;
|
||||||
|
} else if (res.type === 2) {
|
||||||
|
trText = res.data.map((item) => item.dst).join(" ");
|
||||||
|
isSame = to === res.from;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case OPT_TRANS_TENCENT:
|
case OPT_TRANS_TENCENT:
|
||||||
trText = res.auto_translation;
|
trText = res.auto_translation;
|
||||||
isSame = text === trText;
|
isSame = text === trText;
|
||||||
break;
|
break;
|
||||||
case OPT_TRANS_OPENAI:
|
case OPT_TRANS_OPENAI:
|
||||||
trText = res?.choices?.[0].message.content;
|
trText = res?.choices?.map((item) => item.message.content).join(" ");
|
||||||
|
isSame = text === trText;
|
||||||
|
break;
|
||||||
|
case OPT_TRANS_GEMINI:
|
||||||
|
trText = res?.candidates
|
||||||
|
?.map((item) => item.content?.parts.map((item) => item.text).join(" "))
|
||||||
|
.join(" ");
|
||||||
isSame = text === trText;
|
isSame = text === trText;
|
||||||
break;
|
break;
|
||||||
case OPT_TRANS_CLOUDFLAREAI:
|
case OPT_TRANS_CLOUDFLAREAI:
|
||||||
@@ -186,8 +258,31 @@ export const apiTranslate = async ({
|
|||||||
isSame = text === trText;
|
isSame = text === trText;
|
||||||
break;
|
break;
|
||||||
case OPT_TRANS_CUSTOMIZE:
|
case OPT_TRANS_CUSTOMIZE:
|
||||||
|
case OPT_TRANS_CUSTOMIZE_2:
|
||||||
|
case OPT_TRANS_CUSTOMIZE_3:
|
||||||
|
case OPT_TRANS_CUSTOMIZE_4:
|
||||||
|
case OPT_TRANS_CUSTOMIZE_5:
|
||||||
trText = res.text;
|
trText = res.text;
|
||||||
isSame = to === res.from;
|
isSame = to === res.from;
|
||||||
|
|
||||||
|
const { customOption } = apiSetting;
|
||||||
|
if (customOption?.trim()) {
|
||||||
|
try {
|
||||||
|
const opt = JSON.parse(customOption);
|
||||||
|
const textPattern = opt.resPattern?.text;
|
||||||
|
const fromPattern = opt.resPattern?.from;
|
||||||
|
if (textPattern) {
|
||||||
|
trText = textPattern.split(".").reduce((pre, cur) => pre[cur], res);
|
||||||
|
}
|
||||||
|
if (fromPattern) {
|
||||||
|
isSame =
|
||||||
|
to === fromPattern.split(".").reduce((pre, cur) => pre[cur], res);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error(`custom option parse err: ${err}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,17 @@ import {
|
|||||||
MSG_OPEN_OPTIONS,
|
MSG_OPEN_OPTIONS,
|
||||||
MSG_SAVE_RULE,
|
MSG_SAVE_RULE,
|
||||||
MSG_TRANS_TOGGLE_STYLE,
|
MSG_TRANS_TOGGLE_STYLE,
|
||||||
|
MSG_OPEN_TRANBOX,
|
||||||
|
MSG_CONTEXT_MENUS,
|
||||||
|
MSG_COMMAND_SHORTCUTS,
|
||||||
|
MSG_INJECT_JS,
|
||||||
|
MSG_INJECT_CSS,
|
||||||
|
MSG_UPDATE_CSP,
|
||||||
|
DEFAULT_CSPLIST,
|
||||||
CMD_TOGGLE_TRANSLATE,
|
CMD_TOGGLE_TRANSLATE,
|
||||||
CMD_TOGGLE_STYLE,
|
CMD_TOGGLE_STYLE,
|
||||||
CMD_OPEN_OPTIONS,
|
CMD_OPEN_OPTIONS,
|
||||||
|
CMD_OPEN_TRANBOX,
|
||||||
} from "./config";
|
} from "./config";
|
||||||
import { getSettingWithDefault, tryInitDefaultData } from "./libs/storage";
|
import { getSettingWithDefault, tryInitDefaultData } from "./libs/storage";
|
||||||
import { trySyncSettingAndRules } from "./libs/sync";
|
import { trySyncSettingAndRules } from "./libs/sync";
|
||||||
@@ -18,72 +26,184 @@ import { sendTabMsg } from "./libs/msg";
|
|||||||
import { trySyncAllSubRules } from "./libs/subRules";
|
import { trySyncAllSubRules } from "./libs/subRules";
|
||||||
import { tryClearCaches } from "./libs";
|
import { tryClearCaches } from "./libs";
|
||||||
import { saveRule } from "./libs/rules";
|
import { saveRule } from "./libs/rules";
|
||||||
|
import { getCurTabId } from "./libs/msg";
|
||||||
|
import { injectInlineJs, injectInternalCss } from "./libs/injector";
|
||||||
|
import { kissLog } from "./libs/log";
|
||||||
|
|
||||||
globalThis.ContextType = "BACKGROUND";
|
globalThis.ContextType = "BACKGROUND";
|
||||||
|
|
||||||
|
const REMOVE_HEADERS = [
|
||||||
|
`content-security-policy`,
|
||||||
|
`content-security-policy-report-only`,
|
||||||
|
`x-webkit-csp`,
|
||||||
|
`x-content-security-policy`,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加右键菜单
|
||||||
|
*/
|
||||||
|
async function addContextMenus(contextMenuType = 1) {
|
||||||
|
// 添加前先删除,避免重复ID的错误
|
||||||
|
try {
|
||||||
|
await browser.contextMenus.removeAll();
|
||||||
|
} catch (err) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (contextMenuType) {
|
||||||
|
case 1:
|
||||||
|
browser.contextMenus.create({
|
||||||
|
id: CMD_TOGGLE_TRANSLATE,
|
||||||
|
title: browser.i18n.getMessage("app_name"),
|
||||||
|
contexts: ["page", "selection"],
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
browser.contextMenus.create({
|
||||||
|
id: CMD_TOGGLE_TRANSLATE,
|
||||||
|
title: browser.i18n.getMessage("toggle_translate"),
|
||||||
|
contexts: ["page", "selection"],
|
||||||
|
});
|
||||||
|
browser.contextMenus.create({
|
||||||
|
id: CMD_TOGGLE_STYLE,
|
||||||
|
title: browser.i18n.getMessage("toggle_style"),
|
||||||
|
contexts: ["page", "selection"],
|
||||||
|
});
|
||||||
|
browser.contextMenus.create({
|
||||||
|
id: CMD_OPEN_TRANBOX,
|
||||||
|
title: browser.i18n.getMessage("open_tranbox"),
|
||||||
|
contexts: ["page", "selection"],
|
||||||
|
});
|
||||||
|
browser.contextMenus.create({
|
||||||
|
id: "options_separator",
|
||||||
|
type: "separator",
|
||||||
|
contexts: ["page", "selection"],
|
||||||
|
});
|
||||||
|
browser.contextMenus.create({
|
||||||
|
id: CMD_OPEN_OPTIONS,
|
||||||
|
title: browser.i18n.getMessage("open_options"),
|
||||||
|
contexts: ["page", "selection"],
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新CSP策略
|
||||||
|
* @param {*} csplist
|
||||||
|
*/
|
||||||
|
async function updateCspRules(csplist = DEFAULT_CSPLIST.join(",\n")) {
|
||||||
|
try {
|
||||||
|
const newRules = csplist
|
||||||
|
.split(/\n|,/)
|
||||||
|
.map((url) => url.trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((url, idx) => ({
|
||||||
|
id: idx + 1,
|
||||||
|
action: {
|
||||||
|
type: "modifyHeaders",
|
||||||
|
responseHeaders: REMOVE_HEADERS.map((header) => ({
|
||||||
|
operation: "remove",
|
||||||
|
header,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
condition: {
|
||||||
|
urlFilter: url,
|
||||||
|
resourceTypes: ["main_frame", "sub_frame"],
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
const oldRules = await browser.declarativeNetRequest.getDynamicRules();
|
||||||
|
const oldRuleIds = oldRules.map((rule) => rule.id);
|
||||||
|
await browser.declarativeNetRequest.updateDynamicRules({
|
||||||
|
removeRuleIds: oldRuleIds,
|
||||||
|
addRules: newRules,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
kissLog(err, "update csp rules");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 插件安装
|
* 插件安装
|
||||||
*/
|
*/
|
||||||
browser.runtime.onInstalled.addListener(() => {
|
browser.runtime.onInstalled.addListener(() => {
|
||||||
tryInitDefaultData();
|
tryInitDefaultData();
|
||||||
|
|
||||||
|
// 右键菜单
|
||||||
|
addContextMenus();
|
||||||
|
|
||||||
|
// 禁用CSP
|
||||||
|
updateCspRules();
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 浏览器启动
|
* 浏览器启动
|
||||||
*/
|
*/
|
||||||
browser.runtime.onStartup.addListener(async () => {
|
browser.runtime.onStartup.addListener(async () => {
|
||||||
console.log("browser onStartup");
|
|
||||||
|
|
||||||
// 同步数据
|
// 同步数据
|
||||||
await trySyncSettingAndRules();
|
await trySyncSettingAndRules();
|
||||||
|
|
||||||
|
const { clearCache, contextMenuType, subrulesList, csplist } =
|
||||||
|
await getSettingWithDefault();
|
||||||
|
|
||||||
// 清除缓存
|
// 清除缓存
|
||||||
const setting = await getSettingWithDefault();
|
if (clearCache) {
|
||||||
if (setting.clearCache) {
|
|
||||||
tryClearCaches();
|
tryClearCaches();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 右键菜单
|
||||||
|
// firefox重启后菜单会消失,故重复添加
|
||||||
|
addContextMenus(contextMenuType);
|
||||||
|
|
||||||
|
// 禁用CSP
|
||||||
|
updateCspRules(csplist);
|
||||||
|
|
||||||
// 同步订阅规则
|
// 同步订阅规则
|
||||||
trySyncAllSubRules(setting);
|
trySyncAllSubRules({ subrulesList });
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 监听消息
|
* 监听消息
|
||||||
*/
|
*/
|
||||||
browser.runtime.onMessage.addListener(
|
browser.runtime.onMessage.addListener(async ({ action, args }) => {
|
||||||
({ action, args }, sender, sendResponse) => {
|
switch (action) {
|
||||||
switch (action) {
|
case MSG_FETCH:
|
||||||
case MSG_FETCH:
|
const { input, opts } = args;
|
||||||
const { input, opts } = args;
|
return await fetchData(input, opts);
|
||||||
fetchData(input, opts)
|
case MSG_FETCH_LIMIT:
|
||||||
.then((data) => {
|
const { interval, limit } = args;
|
||||||
sendResponse({ data });
|
return fetchPool.update(interval, limit);
|
||||||
})
|
case MSG_FETCH_CLEAR:
|
||||||
.catch((error) => {
|
return fetchPool.clear();
|
||||||
sendResponse({ error: error.message, cause: error.cause });
|
case MSG_OPEN_OPTIONS:
|
||||||
});
|
return await browser.runtime.openOptionsPage();
|
||||||
break;
|
case MSG_SAVE_RULE:
|
||||||
case MSG_FETCH_LIMIT:
|
return await saveRule(args);
|
||||||
const { interval, limit } = args;
|
case MSG_INJECT_JS:
|
||||||
fetchPool.update(interval, limit);
|
return await browser.scripting.executeScript({
|
||||||
sendResponse({ data: "ok" });
|
target: { tabId: await getCurTabId(), allFrames: true },
|
||||||
break;
|
func: injectInlineJs,
|
||||||
case MSG_FETCH_CLEAR:
|
args: [args],
|
||||||
fetchPool.clear();
|
world: "MAIN",
|
||||||
sendResponse({ data: "ok" });
|
});
|
||||||
break;
|
case MSG_INJECT_CSS:
|
||||||
case MSG_OPEN_OPTIONS:
|
return await browser.scripting.executeScript({
|
||||||
browser.runtime.openOptionsPage();
|
target: { tabId: await getCurTabId(), allFrames: true },
|
||||||
break;
|
func: injectInternalCss,
|
||||||
case MSG_SAVE_RULE:
|
args: [args],
|
||||||
saveRule(args);
|
world: "MAIN",
|
||||||
break;
|
});
|
||||||
default:
|
case MSG_UPDATE_CSP:
|
||||||
sendResponse({ error: `message action is unavailable: ${action}` });
|
return await updateCspRules(args);
|
||||||
}
|
case MSG_CONTEXT_MENUS:
|
||||||
return true;
|
return await addContextMenus(args);
|
||||||
|
case MSG_COMMAND_SHORTCUTS:
|
||||||
|
return await browser.commands.getAll();
|
||||||
|
default:
|
||||||
|
throw new Error(`message action is unavailable: ${action}`);
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 监听快捷键
|
* 监听快捷键
|
||||||
@@ -94,6 +214,9 @@ browser.commands.onCommand.addListener((command) => {
|
|||||||
case CMD_TOGGLE_TRANSLATE:
|
case CMD_TOGGLE_TRANSLATE:
|
||||||
sendTabMsg(MSG_TRANS_TOGGLE);
|
sendTabMsg(MSG_TRANS_TOGGLE);
|
||||||
break;
|
break;
|
||||||
|
case CMD_OPEN_TRANBOX:
|
||||||
|
sendTabMsg(MSG_OPEN_TRANBOX);
|
||||||
|
break;
|
||||||
case CMD_TOGGLE_STYLE:
|
case CMD_TOGGLE_STYLE:
|
||||||
sendTabMsg(MSG_TRANS_TOGGLE_STYLE);
|
sendTabMsg(MSG_TRANS_TOGGLE_STYLE);
|
||||||
break;
|
break;
|
||||||
@@ -103,3 +226,24 @@ browser.commands.onCommand.addListener((command) => {
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听右键菜单
|
||||||
|
*/
|
||||||
|
browser.contextMenus.onClicked.addListener(({ menuItemId }) => {
|
||||||
|
switch (menuItemId) {
|
||||||
|
case CMD_TOGGLE_TRANSLATE:
|
||||||
|
sendTabMsg(MSG_TRANS_TOGGLE);
|
||||||
|
break;
|
||||||
|
case CMD_TOGGLE_STYLE:
|
||||||
|
sendTabMsg(MSG_TRANS_TOGGLE_STYLE);
|
||||||
|
break;
|
||||||
|
case CMD_OPEN_TRANBOX:
|
||||||
|
sendTabMsg(MSG_OPEN_TRANBOX);
|
||||||
|
break;
|
||||||
|
case CMD_OPEN_OPTIONS:
|
||||||
|
browser.runtime.openOptionsPage();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
201
src/common.js
201
src/common.js
@@ -8,28 +8,79 @@ import {
|
|||||||
MSG_TRANS_TOGGLE_STYLE,
|
MSG_TRANS_TOGGLE_STYLE,
|
||||||
MSG_TRANS_GETRULE,
|
MSG_TRANS_GETRULE,
|
||||||
MSG_TRANS_PUTRULE,
|
MSG_TRANS_PUTRULE,
|
||||||
|
MSG_OPEN_TRANBOX,
|
||||||
APP_LCNAME,
|
APP_LCNAME,
|
||||||
DEFAULT_TRANBOX_SETTING,
|
DEFAULT_TRANBOX_SETTING,
|
||||||
} from "./config";
|
} from "./config";
|
||||||
import { getRulesWithDefault, getFabWithDefault } from "./libs/storage";
|
import { getFabWithDefault, getSettingWithDefault } from "./libs/storage";
|
||||||
import { Translator } from "./libs/translator";
|
import { Translator } from "./libs/translator";
|
||||||
import { sendIframeMsg, sendParentMsg } from "./libs/iframe";
|
import { isIframe, sendIframeMsg } from "./libs/iframe";
|
||||||
import { matchRule } from "./libs/rules";
|
|
||||||
import Slection from "./views/Selection";
|
import Slection from "./views/Selection";
|
||||||
import { touchTapListener } from "./libs/touch";
|
import { touchTapListener } from "./libs/touch";
|
||||||
import { debounce } from "./libs/utils";
|
import { debounce, genEventName } from "./libs/utils";
|
||||||
|
import { handlePing, injectScript } from "./libs/gm";
|
||||||
|
import { browser } from "./libs/browser";
|
||||||
|
import { matchRule } from "./libs/rules";
|
||||||
|
import { trySyncAllSubRules } from "./libs/subRules";
|
||||||
|
import { isInBlacklist } from "./libs/blacklist";
|
||||||
|
import inputTranslate from "./libs/inputTranslate";
|
||||||
|
|
||||||
export async function runTranslator(setting) {
|
/**
|
||||||
const href = document.location.href;
|
* 油猴脚本设置页面
|
||||||
const rules = await getRulesWithDefault();
|
*/
|
||||||
const rule = await matchRule(rules, href, setting);
|
function runSettingPage() {
|
||||||
const translator = new Translator(rule, setting);
|
if (GM?.info?.script?.grant?.includes("unsafeWindow")) {
|
||||||
|
unsafeWindow.GM = GM;
|
||||||
return { translator, rule };
|
unsafeWindow.APP_INFO = {
|
||||||
|
name: process.env.REACT_APP_NAME,
|
||||||
|
version: process.env.REACT_APP_VERSION,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
const ping = genEventName();
|
||||||
|
window.addEventListener(ping, handlePing);
|
||||||
|
// window.eval(`(${injectScript})("${ping}")`); // eslint-disable-line
|
||||||
|
const script = document.createElement("script");
|
||||||
|
script.textContent = `(${injectScript})("${ping}")`;
|
||||||
|
document.head.append(script);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function runIframe(setting) {
|
/**
|
||||||
let translator;
|
* 插件监听后端事件
|
||||||
|
* @param {*} translator
|
||||||
|
*/
|
||||||
|
function runtimeListener(translator) {
|
||||||
|
browser?.runtime.onMessage.addListener(async ({ action, args }) => {
|
||||||
|
switch (action) {
|
||||||
|
case MSG_TRANS_TOGGLE:
|
||||||
|
translator.toggle();
|
||||||
|
sendIframeMsg(MSG_TRANS_TOGGLE);
|
||||||
|
break;
|
||||||
|
case MSG_TRANS_TOGGLE_STYLE:
|
||||||
|
translator.toggleStyle();
|
||||||
|
sendIframeMsg(MSG_TRANS_TOGGLE_STYLE);
|
||||||
|
break;
|
||||||
|
case MSG_TRANS_GETRULE:
|
||||||
|
break;
|
||||||
|
case MSG_TRANS_PUTRULE:
|
||||||
|
translator.updateRule(args);
|
||||||
|
sendIframeMsg(MSG_TRANS_PUTRULE, args);
|
||||||
|
break;
|
||||||
|
case MSG_OPEN_TRANBOX:
|
||||||
|
window.dispatchEvent(new CustomEvent(MSG_OPEN_TRANBOX));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return { error: `message action is unavailable: ${action}` };
|
||||||
|
}
|
||||||
|
return { data: translator.rule };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* iframe 页面执行
|
||||||
|
* @param {*} translator
|
||||||
|
*/
|
||||||
|
function runIframe(translator) {
|
||||||
window.addEventListener("message", (e) => {
|
window.addEventListener("message", (e) => {
|
||||||
const { action, args } = e.data || {};
|
const { action, args } = e.data || {};
|
||||||
switch (action) {
|
switch (action) {
|
||||||
@@ -40,26 +91,25 @@ export function runIframe(setting) {
|
|||||||
translator?.toggleStyle();
|
translator?.toggleStyle();
|
||||||
break;
|
break;
|
||||||
case MSG_TRANS_PUTRULE:
|
case MSG_TRANS_PUTRULE:
|
||||||
if (!translator) {
|
translator.updateRule(args || {});
|
||||||
translator = new Translator(args, setting);
|
|
||||||
} else {
|
|
||||||
translator.updateRule(args || {});
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
sendParentMsg(MSG_TRANS_GETRULE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function showFab(translator) {
|
/**
|
||||||
|
* 悬浮按钮
|
||||||
|
* @param {*} translator
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async function showFab(translator) {
|
||||||
const fab = await getFabWithDefault();
|
const fab = await getFabWithDefault();
|
||||||
if (fab.isHide) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const $action = document.createElement("div");
|
const $action = document.createElement("div");
|
||||||
$action.setAttribute("id", APP_LCNAME);
|
$action.setAttribute("id", APP_LCNAME);
|
||||||
|
$action.style.fontSize = "0";
|
||||||
|
$action.style.width = "0";
|
||||||
|
$action.style.height = "0";
|
||||||
document.body.parentElement.appendChild($action);
|
document.body.parentElement.appendChild($action);
|
||||||
const shadowContainer = $action.attachShadow({ mode: "closed" });
|
const shadowContainer = $action.attachShadow({ mode: "closed" });
|
||||||
const emotionRoot = document.createElement("style");
|
const emotionRoot = document.createElement("style");
|
||||||
@@ -80,9 +130,16 @@ export async function showFab(translator) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function showTransbox({
|
/**
|
||||||
|
* 划词翻译
|
||||||
|
* @param {*} param0
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function showTransbox({
|
||||||
|
contextMenuType,
|
||||||
tranboxSetting = DEFAULT_TRANBOX_SETTING,
|
tranboxSetting = DEFAULT_TRANBOX_SETTING,
|
||||||
transApis,
|
transApis,
|
||||||
|
darkMode,
|
||||||
}) {
|
}) {
|
||||||
if (!tranboxSetting?.transOpen) {
|
if (!tranboxSetting?.transOpen) {
|
||||||
return;
|
return;
|
||||||
@@ -90,10 +147,15 @@ export function showTransbox({
|
|||||||
|
|
||||||
const $tranbox = document.createElement("div");
|
const $tranbox = document.createElement("div");
|
||||||
$tranbox.setAttribute("id", "kiss-transbox");
|
$tranbox.setAttribute("id", "kiss-transbox");
|
||||||
|
$tranbox.style.fontSize = "0";
|
||||||
|
$tranbox.style.width = "0";
|
||||||
|
$tranbox.style.height = "0";
|
||||||
document.body.parentElement.appendChild($tranbox);
|
document.body.parentElement.appendChild($tranbox);
|
||||||
const shadowContainer = $tranbox.attachShadow({ mode: "closed" });
|
const shadowContainer = $tranbox.attachShadow({ mode: "closed" });
|
||||||
const emotionRoot = document.createElement("style");
|
const emotionRoot = document.createElement("style");
|
||||||
const shadowRootElement = document.createElement("div");
|
const shadowRootElement = document.createElement("div");
|
||||||
|
shadowRootElement.classList.add(`KT-transbox`);
|
||||||
|
shadowRootElement.classList.add(`KT-transbox_${darkMode ? "dark" : "light"}`);
|
||||||
shadowContainer.appendChild(emotionRoot);
|
shadowContainer.appendChild(emotionRoot);
|
||||||
shadowContainer.appendChild(shadowRootElement);
|
shadowContainer.appendChild(shadowRootElement);
|
||||||
const cache = createCache({
|
const cache = createCache({
|
||||||
@@ -104,32 +166,33 @@ export function showTransbox({
|
|||||||
ReactDOM.createRoot(shadowRootElement).render(
|
ReactDOM.createRoot(shadowRootElement).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<CacheProvider value={cache}>
|
<CacheProvider value={cache}>
|
||||||
<Slection tranboxSetting={tranboxSetting} transApis={transApis} />
|
<Slection
|
||||||
|
contextMenuType={contextMenuType}
|
||||||
|
tranboxSetting={tranboxSetting}
|
||||||
|
transApis={transApis}
|
||||||
|
/>
|
||||||
</CacheProvider>
|
</CacheProvider>
|
||||||
</React.StrictMode>
|
</React.StrictMode>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function windowListener(rule) {
|
/**
|
||||||
window.addEventListener("message", (e) => {
|
* 显示错误信息到页面顶部
|
||||||
const { action } = e.data || {};
|
* @param {*} message
|
||||||
switch (action) {
|
*/
|
||||||
case MSG_TRANS_GETRULE:
|
function showErr(message) {
|
||||||
sendIframeMsg(MSG_TRANS_PUTRULE, rule);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function showErr(message) {
|
|
||||||
const $err = document.createElement("div");
|
const $err = document.createElement("div");
|
||||||
$err.innerText = `KISS-Translator: ${message}`;
|
$err.innerText = `KISS-Translator: ${message}`;
|
||||||
$err.style.cssText = "background:red; color:#fff;";
|
$err.style.cssText = "background:red; color:#fff;";
|
||||||
document.body.prepend($err);
|
document.body.prepend($err);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function touchOperation(translator) {
|
/**
|
||||||
|
* 监听触屏操作
|
||||||
|
* @param {*} translator
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function touchOperation(translator) {
|
||||||
const { touchTranslate = 2 } = translator.setting;
|
const { touchTranslate = 2 } = translator.setting;
|
||||||
if (touchTranslate === 0) {
|
if (touchTranslate === 0) {
|
||||||
return;
|
return;
|
||||||
@@ -141,3 +204,61 @@ export function touchOperation(translator) {
|
|||||||
});
|
});
|
||||||
touchTapListener(handleTap, touchTranslate);
|
touchTapListener(handleTap, touchTranslate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 入口函数
|
||||||
|
*/
|
||||||
|
export async function run(isUserscript = false) {
|
||||||
|
try {
|
||||||
|
const href = document.location.href;
|
||||||
|
|
||||||
|
// 设置页面
|
||||||
|
if (
|
||||||
|
isUserscript &&
|
||||||
|
(href.includes(process.env.REACT_APP_OPTIONSPAGE_DEV) ||
|
||||||
|
href.includes(process.env.REACT_APP_OPTIONSPAGE))
|
||||||
|
) {
|
||||||
|
runSettingPage();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取设置信息
|
||||||
|
const setting = await getSettingWithDefault();
|
||||||
|
|
||||||
|
// 黑名单
|
||||||
|
if (isInBlacklist(href, setting)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 翻译网页
|
||||||
|
const rule = await matchRule(href, setting);
|
||||||
|
const translator = new Translator(rule, setting);
|
||||||
|
|
||||||
|
// 适配iframe
|
||||||
|
if (isIframe) {
|
||||||
|
runIframe(translator);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听消息
|
||||||
|
!isUserscript && runtimeListener(translator);
|
||||||
|
|
||||||
|
// 输入框翻译
|
||||||
|
inputTranslate(setting);
|
||||||
|
|
||||||
|
// 划词翻译
|
||||||
|
showTransbox(setting);
|
||||||
|
|
||||||
|
// 浮球按钮
|
||||||
|
await showFab(translator);
|
||||||
|
|
||||||
|
// 触屏操作
|
||||||
|
touchOperation(translator);
|
||||||
|
|
||||||
|
// 同步订阅规则
|
||||||
|
isUserscript && (await trySyncAllSubRules(setting));
|
||||||
|
} catch (err) {
|
||||||
|
console.error("[KISS-Translator]", err);
|
||||||
|
showErr(err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -42,24 +42,28 @@ const customApiLangs = `["en", "English - English"],
|
|||||||
["vi", "Vietnamese - Tiếng Việt"],
|
["vi", "Vietnamese - Tiếng Việt"],
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const customApiHelpZH = `/// 自定义翻译源接口说明
|
const customDefaultOption = `{
|
||||||
|
"url": "{{url}}",
|
||||||
// 请求(Request)数据将按下面规范发送
|
"method": "POST",
|
||||||
{
|
"headers": {
|
||||||
url: {{YOUR_URL}},
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-type": "application/json",
|
"Content-type": "application/json",
|
||||||
"Authorization": "Bearer {{YOUR_KEY}}",
|
"Authorization": "Bearer {{key}}"
|
||||||
},
|
},
|
||||||
body: {
|
"body": {
|
||||||
text: "", // 需要翻译的文字
|
"text": "{{text}}",
|
||||||
from: "", // 源语言,可能为空,表示需要接口自动识别语言
|
"from": "{{from}}",
|
||||||
to: "", // 目标语言
|
"to": "{{to}}"
|
||||||
|
},
|
||||||
|
"resPattern": {
|
||||||
|
"text": "text",
|
||||||
|
"from": "from"
|
||||||
}
|
}
|
||||||
}
|
}`;
|
||||||
|
|
||||||
// 返回(Response)数据需符合下面的JSON规范
|
const customApiHelpZH = `// 自定义选项范例
|
||||||
|
${customDefaultOption}
|
||||||
|
|
||||||
|
// 返回数据默认格式
|
||||||
{
|
{
|
||||||
text: "", // 翻译后的文字
|
text: "", // 翻译后的文字
|
||||||
from: "", // 识别的源语言
|
from: "", // 识别的源语言
|
||||||
@@ -70,24 +74,10 @@ const customApiHelpZH = `/// 自定义翻译源接口说明
|
|||||||
${customApiLangs}
|
${customApiLangs}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const customApiHelpEN = `/// Custom translation source interface description
|
const customApiHelpEN = `// Example of custom options
|
||||||
|
${customDefaultOption}
|
||||||
|
|
||||||
// Request data will be sent according to the following specifications
|
// Return data default format
|
||||||
{
|
|
||||||
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
|
text: "", // translated text
|
||||||
from: "", // Recognized source language
|
from: "", // Recognized source language
|
||||||
@@ -112,8 +102,8 @@ export const I18N = {
|
|||||||
en: customApiHelpEN,
|
en: customApiHelpEN,
|
||||||
},
|
},
|
||||||
translate_alt: {
|
translate_alt: {
|
||||||
zh: `翻译 (Alt+Q)`,
|
zh: `翻译`,
|
||||||
en: `Translate (Alt+Q)`,
|
en: `Translate`,
|
||||||
},
|
},
|
||||||
basic_setting: {
|
basic_setting: {
|
||||||
zh: `基本设置`,
|
zh: `基本设置`,
|
||||||
@@ -160,20 +150,24 @@ export const I18N = {
|
|||||||
en: `Interface Language`,
|
en: `Interface Language`,
|
||||||
},
|
},
|
||||||
fetch_limit: {
|
fetch_limit: {
|
||||||
zh: `最大请求数量 (1-100)`,
|
zh: `最大并发请求数量 (1-100)`,
|
||||||
en: `Maximum Number Of Request (1-100)`,
|
en: `Maximum Number Of Concurrent Requests (1-100)`,
|
||||||
},
|
},
|
||||||
fetch_interval: {
|
fetch_interval: {
|
||||||
zh: `请求间隔时间 (0-5000ms)`,
|
zh: `每次请求间隔时间 (0-5000ms)`,
|
||||||
en: `Request Interval (0-5000ms)`,
|
en: `Time Between Requests (0-5000ms)`,
|
||||||
|
},
|
||||||
|
translate_interval: {
|
||||||
|
zh: `重新翻译间隔时间 (100-5000ms)`,
|
||||||
|
en: `Retranslation Interval (100-5000ms)`,
|
||||||
},
|
},
|
||||||
min_translate_length: {
|
min_translate_length: {
|
||||||
zh: `最小翻译长度 (1-100)`,
|
zh: `最小翻译字符数 (1-100)`,
|
||||||
en: `Min Translate Length (1-100)`,
|
en: `Minimum number Of Translated Characters (1-100)`,
|
||||||
},
|
},
|
||||||
max_translate_length: {
|
max_translate_length: {
|
||||||
zh: `最大翻译长度 (100-10000)`,
|
zh: `最大翻译字符数 (100-10000)`,
|
||||||
en: `Max Translate Length (100-10000)`,
|
en: `Maximum number Of Translated Characters (100-10000)`,
|
||||||
},
|
},
|
||||||
num_of_newline_characters: {
|
num_of_newline_characters: {
|
||||||
zh: `换行字符数 (1-1000)`,
|
zh: `换行字符数 (1-1000)`,
|
||||||
@@ -183,16 +177,20 @@ export const I18N = {
|
|||||||
zh: `翻译服务`,
|
zh: `翻译服务`,
|
||||||
en: `Translate Service`,
|
en: `Translate Service`,
|
||||||
},
|
},
|
||||||
mouseover_translation: {
|
translate_timing: {
|
||||||
zh: `鼠标悬停翻译`,
|
zh: `翻译时机`,
|
||||||
en: `Mouseover translation`,
|
en: `Translate Timing`,
|
||||||
},
|
},
|
||||||
mk_disable: {
|
mk_pagescroll: {
|
||||||
zh: `禁用`,
|
zh: `滚动加载翻译(推荐)`,
|
||||||
en: `Disable`,
|
en: `Rolling Loading (Suggested)`,
|
||||||
|
},
|
||||||
|
mk_pageopen: {
|
||||||
|
zh: `页面打开全部翻译`,
|
||||||
|
en: `Page Open`,
|
||||||
},
|
},
|
||||||
mk_mouseover: {
|
mk_mouseover: {
|
||||||
zh: `鼠标悬停`,
|
zh: `鼠标悬停翻译`,
|
||||||
en: `Mouseover`,
|
en: `Mouseover`,
|
||||||
},
|
},
|
||||||
mk_ctrlKey: {
|
mk_ctrlKey: {
|
||||||
@@ -215,13 +213,21 @@ export const I18N = {
|
|||||||
zh: `目标语言`,
|
zh: `目标语言`,
|
||||||
en: `Target Language`,
|
en: `Target Language`,
|
||||||
},
|
},
|
||||||
|
to_lang2: {
|
||||||
|
zh: `第二目标语言`,
|
||||||
|
en: `Target Language 2`,
|
||||||
|
},
|
||||||
|
to_lang2_helper: {
|
||||||
|
zh: `设定后,与目标语言产生互译效果,但依赖远程语言识别。`,
|
||||||
|
en: `After setting, it will produce mutual translation effect with the target language, but it relies on remote language recognition.`,
|
||||||
|
},
|
||||||
text_style: {
|
text_style: {
|
||||||
zh: `文字样式`,
|
zh: `译文样式`,
|
||||||
en: `Text Style`,
|
en: `Text Style`,
|
||||||
},
|
},
|
||||||
text_style_alt: {
|
text_style_alt: {
|
||||||
zh: `文字样式 (Alt+C)`,
|
zh: `译文样式`,
|
||||||
en: `Text Style (Alt+C)`,
|
en: `Text Style`,
|
||||||
},
|
},
|
||||||
bg_color: {
|
bg_color: {
|
||||||
zh: `样式颜色`,
|
zh: `样式颜色`,
|
||||||
@@ -295,6 +301,10 @@ export const I18N = {
|
|||||||
zh: `2、“订阅规则”的注入位置是倒数第二的位置,因此除全局规则(*)外,“个人规则”优先级比“订阅规则”高,“个人规则”填写同样的网址会覆盖”订阅规则“的条目。`,
|
zh: `2、“订阅规则”的注入位置是倒数第二的位置,因此除全局规则(*)外,“个人规则”优先级比“订阅规则”高,“个人规则”填写同样的网址会覆盖”订阅规则“的条目。`,
|
||||||
en: `2. The injection position of "Subscription Rules" is the penultimate position. Therefore, except for the global rules (*), the priority of "Personal Rules" is higher than that of "Subscription Rules". Filling in the same url in "Personal Rules" will overwrite "Subscription Rules" entry.`,
|
en: `2. The injection position of "Subscription Rules" is the penultimate position. Therefore, except for the global rules (*), the priority of "Personal Rules" is higher than that of "Subscription Rules". Filling in the same url in "Personal Rules" will overwrite "Subscription Rules" entry.`,
|
||||||
},
|
},
|
||||||
|
rules_warn_3: {
|
||||||
|
zh: `3、关于规则填写:输入框留空或下拉框选“*”表示采用全局规则。`,
|
||||||
|
en: `3. Regarding filling in the rules: Leave the input box blank or select "*" in the drop-down box to use global rule.`,
|
||||||
|
},
|
||||||
sync_warn: {
|
sync_warn: {
|
||||||
zh: `如果服务器存在其他客户端同步的数据,第一次同步将直接覆盖本地配置,后面则根据修改时间,新的覆盖旧的。`,
|
zh: `如果服务器存在其他客户端同步的数据,第一次同步将直接覆盖本地配置,后面则根据修改时间,新的覆盖旧的。`,
|
||||||
en: `If the server has data synchronized by other clients, the first synchronization will directly overwrite the local configuration, and later, according to the modification time, the new one will overwrite the old one.`,
|
en: `If the server has data synchronized by other clients, the first synchronization will directly overwrite the local configuration, and later, according to the modification time, the new one will overwrite the old one.`,
|
||||||
@@ -335,6 +345,10 @@ export const I18N = {
|
|||||||
zh: `高亮`,
|
zh: `高亮`,
|
||||||
en: `Highlight`,
|
en: `Highlight`,
|
||||||
},
|
},
|
||||||
|
blockquote: {
|
||||||
|
zh: `引用`,
|
||||||
|
en: `Blockquote`,
|
||||||
|
},
|
||||||
diy_style: {
|
diy_style: {
|
||||||
zh: `自定义样式`,
|
zh: `自定义样式`,
|
||||||
en: `Custom Style`,
|
en: `Custom Style`,
|
||||||
@@ -344,20 +358,20 @@ export const I18N = {
|
|||||||
en: `Follow the syntax of "CSS"`,
|
en: `Follow the syntax of "CSS"`,
|
||||||
},
|
},
|
||||||
setting: {
|
setting: {
|
||||||
zh: `设置 (Alt+O)`,
|
zh: `设置`,
|
||||||
en: `Setting (Alt+O)`,
|
en: `Setting`,
|
||||||
},
|
},
|
||||||
pattern: {
|
pattern: {
|
||||||
zh: `匹配网址`,
|
zh: `匹配网址`,
|
||||||
en: `URL pattern`,
|
en: `URL pattern`,
|
||||||
},
|
},
|
||||||
pattern_helper: {
|
pattern_helper: {
|
||||||
zh: `1、支持星号(*)通配符。2、多个URL支持英文逗号“,”分隔。`,
|
zh: `1、支持星号(*)通配符。2、多个URL用换行或英文逗号“,”分隔。`,
|
||||||
en: `1. The asterisk (*) wildcard is supported. 2. Multiple URLs can be separated by English commas ",".`,
|
en: `1. Supports the asterisk (*) wildcard character. 2. Separate multiple URLs with newlines or English commas ",".`,
|
||||||
},
|
},
|
||||||
selector_helper: {
|
selector_helper: {
|
||||||
zh: `1、遵循CSS选择器语法。2、留空表示采用全局设置。3、多个CSS选择器之间用“;”隔开。4、“shadow root”选择器和内部选择器用“>>>”隔开。`,
|
zh: `1、遵循CSS选择器语法。2、多个CSS选择器之间用“;”隔开。3、“shadow root”选择器和内部选择器用“>>>”隔开。`,
|
||||||
en: `1. Follow CSS selector syntax. 2. Leave blank to adopt the global setting. 3. Separate multiple CSS selectors with ";". 4. The "shadow root" selector and the internal selector are separated by ">>>".`,
|
en: `1. Follow CSS selector syntax. 2. Separate multiple CSS selectors with ";". 3. The "shadow root" selector and the internal selector are separated by ">>>".`,
|
||||||
},
|
},
|
||||||
translate_switch: {
|
translate_switch: {
|
||||||
zh: `开启翻译`,
|
zh: `开启翻译`,
|
||||||
@@ -375,6 +389,50 @@ export const I18N = {
|
|||||||
zh: `选择器`,
|
zh: `选择器`,
|
||||||
en: `Selector`,
|
en: `Selector`,
|
||||||
},
|
},
|
||||||
|
keep_selector: {
|
||||||
|
zh: `保留元素选择器`,
|
||||||
|
en: `Keep unchanged selector`,
|
||||||
|
},
|
||||||
|
keep_selector_helper: {
|
||||||
|
zh: `1、遵循CSS选择器语法。2、子元素选择器用“>>>”隔开。`,
|
||||||
|
en: `1. Follow CSS selector syntax. 2. Sub-element selectors are separated by ">>>".`,
|
||||||
|
},
|
||||||
|
terms: {
|
||||||
|
zh: `专业术语`,
|
||||||
|
en: `Terms`,
|
||||||
|
},
|
||||||
|
terms_helper: {
|
||||||
|
zh: `1、支持正则表达式匹配,无需斜杆,不支持修饰符。2、多条术语用换行或分号“;”隔开。3、术语和译文用英文逗号“,”隔开。4、没有译文视为不翻译术语。`,
|
||||||
|
en: `1. Supports regular expression matching, no slash required, and no modifiers are supported. 2. Separate multiple terms with newlines or semicolons ";". 3. Terms and translations are separated by English commas ",". 4. If there is no translation, the term will be deemed not to be translated.`,
|
||||||
|
},
|
||||||
|
selector_style: {
|
||||||
|
zh: `选择器节点样式`,
|
||||||
|
en: `Selector Style`,
|
||||||
|
},
|
||||||
|
selector_style_helper: {
|
||||||
|
zh: `开启翻译时注入,关闭翻译时不会移除。`,
|
||||||
|
en: `It is injected when translation is turned on and will not be removed when translation is turned off.`,
|
||||||
|
},
|
||||||
|
selector_parent_style: {
|
||||||
|
zh: `选择器父节点样式`,
|
||||||
|
en: `Selector Parent Style`,
|
||||||
|
},
|
||||||
|
inject_js: {
|
||||||
|
zh: `注入JS`,
|
||||||
|
en: `Inject JS`,
|
||||||
|
},
|
||||||
|
inject_js_helper: {
|
||||||
|
zh: `1、开启翻译时注入运行,关闭翻译时移除。2、随着页面变化,可能会多次注入运行。`,
|
||||||
|
en: `1. Inject and run when translation is turned on, and removed when translation is turned off. 2. As the page changes, it may be injected and run multiple times.`,
|
||||||
|
},
|
||||||
|
inject_css: {
|
||||||
|
zh: `注入CSS`,
|
||||||
|
en: `Inject CSS`,
|
||||||
|
},
|
||||||
|
inject_css_helper: {
|
||||||
|
zh: `开启翻译时注入,关闭翻译时将移除。`,
|
||||||
|
en: `Injected when translation is enabled and removed when translation is disabled.`,
|
||||||
|
},
|
||||||
root_selector: {
|
root_selector: {
|
||||||
zh: `根选择器`,
|
zh: `根选择器`,
|
||||||
en: `Root Selector`,
|
en: `Root Selector`,
|
||||||
@@ -383,6 +441,10 @@ export const I18N = {
|
|||||||
zh: `修复函数`,
|
zh: `修复函数`,
|
||||||
en: `Fixer Function`,
|
en: `Fixer Function`,
|
||||||
},
|
},
|
||||||
|
fixer_function_helper: {
|
||||||
|
zh: `1、br是将<br>换行替换成<p "kiss-p">。2、bn是将\\n换行替换成<p "kiss-p">。3、brToDiv和bnToDiv是替换成<div class="kiss-p">。`,
|
||||||
|
en: `1. br replaces <br> line breaks with <p "kiss-p">. 2. bn replaces \\n newline with <p "kiss-p">. 3. brToDiv and bnToDiv are replaced with <div class="kiss-p">.`,
|
||||||
|
},
|
||||||
import: {
|
import: {
|
||||||
zh: `导入`,
|
zh: `导入`,
|
||||||
en: `Import`,
|
en: `Import`,
|
||||||
@@ -391,6 +453,10 @@ export const I18N = {
|
|||||||
zh: `导出`,
|
zh: `导出`,
|
||||||
en: `Export`,
|
en: `Export`,
|
||||||
},
|
},
|
||||||
|
export_translation: {
|
||||||
|
zh: `导出释义`,
|
||||||
|
en: `Export Translation`,
|
||||||
|
},
|
||||||
error_cant_be_blank: {
|
error_cant_be_blank: {
|
||||||
zh: `不能为空`,
|
zh: `不能为空`,
|
||||||
en: `Can not be blank`,
|
en: `Can not be blank`,
|
||||||
@@ -547,6 +613,18 @@ export const I18N = {
|
|||||||
zh: `隐藏悬浮按钮`,
|
zh: `隐藏悬浮按钮`,
|
||||||
en: `Hide Fab Button`,
|
en: `Hide Fab Button`,
|
||||||
},
|
},
|
||||||
|
hide_tran_button: {
|
||||||
|
zh: `隐藏翻译按钮`,
|
||||||
|
en: `Hide Translate Button`,
|
||||||
|
},
|
||||||
|
hide_click_away: {
|
||||||
|
zh: `点击外部关闭弹窗`,
|
||||||
|
en: `Click outside to close the pop-up window`,
|
||||||
|
},
|
||||||
|
use_simple_style: {
|
||||||
|
zh: `使用简洁界面`,
|
||||||
|
en: `Click outside to close the pop-up window`,
|
||||||
|
},
|
||||||
show: {
|
show: {
|
||||||
zh: `显示`,
|
zh: `显示`,
|
||||||
en: `Show`,
|
en: `Show`,
|
||||||
@@ -628,8 +706,8 @@ export const I18N = {
|
|||||||
en: `Use Selection Translate`,
|
en: `Use Selection Translate`,
|
||||||
},
|
},
|
||||||
trigger_tranbox_shortcut: {
|
trigger_tranbox_shortcut: {
|
||||||
zh: `显示翻译框快捷键`,
|
zh: `显示翻译框/翻译选中文字快捷键`,
|
||||||
en: `Toggle Translate Box Shortcut`,
|
en: `Open Translate Popup/Translate Selected Shortcut`,
|
||||||
},
|
},
|
||||||
tranbtn_offset_x: {
|
tranbtn_offset_x: {
|
||||||
zh: `翻译按钮偏移X(0-100)`,
|
zh: `翻译按钮偏移X(0-100)`,
|
||||||
@@ -671,4 +749,100 @@ export const I18N = {
|
|||||||
zh: `三指轻触`,
|
zh: `三指轻触`,
|
||||||
en: `Three finger tap`,
|
en: `Three finger tap`,
|
||||||
},
|
},
|
||||||
|
touch_tap_4: {
|
||||||
|
zh: `四指轻触`,
|
||||||
|
en: `Four finger tap`,
|
||||||
|
},
|
||||||
|
translate_blacklist: {
|
||||||
|
zh: `禁用翻译名单`,
|
||||||
|
en: `Translate Blacklist`,
|
||||||
|
},
|
||||||
|
disabled_csplist: {
|
||||||
|
zh: `禁用CSP名单`,
|
||||||
|
en: `Disabled CSP List`,
|
||||||
|
},
|
||||||
|
disabled_csplist_helper: {
|
||||||
|
zh: `3、通过调整CSP策略,使得某些页面能够注入JS/CSS/Media,请谨慎使用,除非您已知晓相关风险。`,
|
||||||
|
en: `3. By adjusting the CSP policy, some pages can inject JS/CSS/Media. Please use it with caution unless you are aware of the related risks.`,
|
||||||
|
},
|
||||||
|
skip_langs: {
|
||||||
|
zh: `不翻译的语言`,
|
||||||
|
en: `Disable Languages`,
|
||||||
|
},
|
||||||
|
skip_langs_helper: {
|
||||||
|
zh: `此功能依赖准确的语言检测,建议启用远程语言检测。`,
|
||||||
|
en: `This feature relies on accurate language detection. It is recommended to enable remote language detection.`,
|
||||||
|
},
|
||||||
|
context_menus: {
|
||||||
|
zh: `右键菜单`,
|
||||||
|
en: `Context Menus`,
|
||||||
|
},
|
||||||
|
hide_context_menus: {
|
||||||
|
zh: `隐藏右键菜单`,
|
||||||
|
en: `Hide Context Menus`,
|
||||||
|
},
|
||||||
|
simple_context_menus: {
|
||||||
|
zh: `简单右键菜单`,
|
||||||
|
en: `Simple_context_menus Context Menus`,
|
||||||
|
},
|
||||||
|
secondary_context_menus: {
|
||||||
|
zh: `二级右键菜单`,
|
||||||
|
en: `Secondary Context Menus`,
|
||||||
|
},
|
||||||
|
mulkeys_help: {
|
||||||
|
zh: `支持用换行或英文逗号“,”分隔多个KEY轮询调用。`,
|
||||||
|
en: `Supports multiple KEY polling calls separated by newlines or English commas ",".`,
|
||||||
|
},
|
||||||
|
translation_element_tag: {
|
||||||
|
zh: `译文元素标签`,
|
||||||
|
en: `Translation Element Tag`,
|
||||||
|
},
|
||||||
|
show_only_translations: {
|
||||||
|
zh: `仅显示译文`,
|
||||||
|
en: `Show Only Translations`,
|
||||||
|
},
|
||||||
|
show_only_translations_help: {
|
||||||
|
zh: `非完美实现,某些页面可能有样式等问题。`,
|
||||||
|
en: `It is not a perfect implementation and some pages may have style issues.`,
|
||||||
|
},
|
||||||
|
translate_page_title: {
|
||||||
|
zh: `是否翻译页面标题`,
|
||||||
|
en: `Translate Page Title`,
|
||||||
|
},
|
||||||
|
more: {
|
||||||
|
zh: `更多`,
|
||||||
|
en: `More`,
|
||||||
|
},
|
||||||
|
fixer_selector: {
|
||||||
|
zh: `网页修复选择器`,
|
||||||
|
en: `Fixer Selector`,
|
||||||
|
},
|
||||||
|
reg_niutrans: {
|
||||||
|
zh: `获取小牛翻译密钥`,
|
||||||
|
en: `Get NiuTrans APIKey`,
|
||||||
|
},
|
||||||
|
trigger_mode: {
|
||||||
|
zh: `触发方式`,
|
||||||
|
en: `Trigger Mode`,
|
||||||
|
},
|
||||||
|
trigger_click: {
|
||||||
|
zh: `点击触发`,
|
||||||
|
en: `Click Trigger`,
|
||||||
|
},
|
||||||
|
trigger_hover: {
|
||||||
|
zh: `鼠标悬停触发`,
|
||||||
|
en: `Hover Trigger`,
|
||||||
|
},
|
||||||
|
trigger_select: {
|
||||||
|
zh: `选中触发`,
|
||||||
|
en: `Select Trigger`,
|
||||||
|
},
|
||||||
|
extend_styles: {
|
||||||
|
zh: `附加样式`,
|
||||||
|
en: `Extend Styles`,
|
||||||
|
},
|
||||||
|
custom_option: {
|
||||||
|
zh: `自定义选项`,
|
||||||
|
en: `Custom Option`,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
DEFAULT_SELECTOR,
|
DEFAULT_SELECTOR,
|
||||||
|
DEFAULT_KEEP_SELECTOR,
|
||||||
GLOBAL_KEY,
|
GLOBAL_KEY,
|
||||||
REMAIN_KEY,
|
REMAIN_KEY,
|
||||||
SHADOW_KEY,
|
SHADOW_KEY,
|
||||||
@@ -23,16 +24,15 @@ export const STOKEY_MSAUTH = `${APP_NAME}_msauth`;
|
|||||||
export const STOKEY_BDAUTH = `${APP_NAME}_bdauth`;
|
export const STOKEY_BDAUTH = `${APP_NAME}_bdauth`;
|
||||||
export const STOKEY_SETTING = `${APP_NAME}_setting`;
|
export const STOKEY_SETTING = `${APP_NAME}_setting`;
|
||||||
export const STOKEY_RULES = `${APP_NAME}_rules`;
|
export const STOKEY_RULES = `${APP_NAME}_rules`;
|
||||||
export const STOKEY_WFRULES = `${APP_NAME}_webfix_rules`;
|
|
||||||
export const STOKEY_WORDS = `${APP_NAME}_words`;
|
export const STOKEY_WORDS = `${APP_NAME}_words`;
|
||||||
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";
|
||||||
export const CMD_OPEN_OPTIONS = "openOptions";
|
export const CMD_OPEN_OPTIONS = "openOptions";
|
||||||
|
export const CMD_OPEN_TRANBOX = "openTranbox";
|
||||||
|
|
||||||
export const CLIENT_WEB = "web";
|
export const CLIENT_WEB = "web";
|
||||||
export const CLIENT_CHROME = "chrome";
|
export const CLIENT_CHROME = "chrome";
|
||||||
@@ -42,7 +42,6 @@ export const CLIENT_USERSCRIPT = "userscript";
|
|||||||
export const CLIENT_EXTS = [CLIENT_CHROME, CLIENT_EDGE, CLIENT_FIREFOX];
|
export const CLIENT_EXTS = [CLIENT_CHROME, CLIENT_EDGE, CLIENT_FIREFOX];
|
||||||
|
|
||||||
export const KV_RULES_KEY = "kiss-rules.json";
|
export const KV_RULES_KEY = "kiss-rules.json";
|
||||||
export const KV_WFRULES_KEY = "kiss-webfix.json";
|
|
||||||
export const KV_WORDS_KEY = "kiss-words.json";
|
export const KV_WORDS_KEY = "kiss-words.json";
|
||||||
export const KV_RULES_SHARE_KEY = "kiss-rules-share.json";
|
export const KV_RULES_SHARE_KEY = "kiss-rules-share.json";
|
||||||
export const KV_SETTING_KEY = "kiss-setting.json";
|
export const KV_SETTING_KEY = "kiss-setting.json";
|
||||||
@@ -58,9 +57,15 @@ export const MSG_OPEN_OPTIONS = "open_options";
|
|||||||
export const MSG_SAVE_RULE = "save_rule";
|
export const MSG_SAVE_RULE = "save_rule";
|
||||||
export const MSG_TRANS_TOGGLE = "trans_toggle";
|
export const MSG_TRANS_TOGGLE = "trans_toggle";
|
||||||
export const MSG_TRANS_TOGGLE_STYLE = "trans_toggle_style";
|
export const MSG_TRANS_TOGGLE_STYLE = "trans_toggle_style";
|
||||||
|
export const MSG_OPEN_TRANBOX = "open_tranbox";
|
||||||
export const MSG_TRANS_GETRULE = "trans_getrule";
|
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 MSG_CONTEXT_MENUS = "context_menus";
|
||||||
|
export const MSG_COMMAND_SHORTCUTS = "command_shortcuts";
|
||||||
|
export const MSG_INJECT_JS = "inject_js";
|
||||||
|
export const MSG_INJECT_CSS = "inject_css";
|
||||||
|
export const MSG_UPDATE_CSP = "update_csp";
|
||||||
|
|
||||||
export const THEME_LIGHT = "light";
|
export const THEME_LIGHT = "light";
|
||||||
export const THEME_DARK = "dark";
|
export const THEME_DARK = "dark";
|
||||||
@@ -78,32 +83,52 @@ export const URL_MICROSOFT_TRAN =
|
|||||||
"https://api-edge.cognitive.microsofttranslator.com/translate";
|
"https://api-edge.cognitive.microsofttranslator.com/translate";
|
||||||
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_BAIDU_LANGDETECT = "https://fanyi.baidu.com/langdetect";
|
export const URL_BAIDU_LANGDETECT = "https://fanyi.baidu.com/langdetect";
|
||||||
|
export const URL_BAIDU_SUGGEST = "https://fanyi.baidu.com/sug";
|
||||||
|
export const URL_BAIDU_TTS = "https://fanyi.baidu.com/gettts";
|
||||||
export const URL_BAIDU_WEB = "https://fanyi.baidu.com/";
|
export const URL_BAIDU_WEB = "https://fanyi.baidu.com/";
|
||||||
export const URL_BAIDU_TRAN = "https://fanyi.baidu.com/v2transapi";
|
export const URL_BAIDU_TRANSAPI = "https://fanyi.baidu.com/transapi";
|
||||||
|
export const URL_BAIDU_TRANSAPI_V2 = "https://fanyi.baidu.com/v2transapi";
|
||||||
export const URL_DEEPLFREE_TRAN = "https://www2.deepl.com/jsonrpc";
|
export const URL_DEEPLFREE_TRAN = "https://www2.deepl.com/jsonrpc";
|
||||||
export const URL_TENCENT_TRANSMART = "https://transmart.qq.com/api/imt";
|
export const URL_TENCENT_TRANSMART = "https://transmart.qq.com/api/imt";
|
||||||
|
export const URL_NIUTRANS_REG =
|
||||||
|
"https://niutrans.com/login?active=3&userSource=kiss-translator";
|
||||||
|
|
||||||
|
export const DEFAULT_USER_AGENT =
|
||||||
|
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36";
|
||||||
|
|
||||||
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_DEEPL = "DeepL";
|
||||||
export const OPT_TRANS_DEEPLX = "DeepLX";
|
export const OPT_TRANS_DEEPLX = "DeepLX";
|
||||||
export const OPT_TRANS_DEEPLFREE = "DeepLFree";
|
export const OPT_TRANS_DEEPLFREE = "DeepLFree";
|
||||||
|
export const OPT_TRANS_NIUTRANS = "NiuTrans";
|
||||||
export const OPT_TRANS_BAIDU = "Baidu";
|
export const OPT_TRANS_BAIDU = "Baidu";
|
||||||
export const OPT_TRANS_TENCENT = "Tencent";
|
export const OPT_TRANS_TENCENT = "Tencent";
|
||||||
export const OPT_TRANS_OPENAI = "OpenAI";
|
export const OPT_TRANS_OPENAI = "OpenAI";
|
||||||
|
export const OPT_TRANS_GEMINI = "Gemini";
|
||||||
export const OPT_TRANS_CLOUDFLAREAI = "CloudflareAI";
|
export const OPT_TRANS_CLOUDFLAREAI = "CloudflareAI";
|
||||||
export const OPT_TRANS_CUSTOMIZE = "Custom";
|
export const OPT_TRANS_CUSTOMIZE = "Custom";
|
||||||
|
export const OPT_TRANS_CUSTOMIZE_2 = "Custom2";
|
||||||
|
export const OPT_TRANS_CUSTOMIZE_3 = "Custom3";
|
||||||
|
export const OPT_TRANS_CUSTOMIZE_4 = "Custom4";
|
||||||
|
export const OPT_TRANS_CUSTOMIZE_5 = "Custom5";
|
||||||
export const OPT_TRANS_ALL = [
|
export const OPT_TRANS_ALL = [
|
||||||
OPT_TRANS_GOOGLE,
|
OPT_TRANS_GOOGLE,
|
||||||
OPT_TRANS_MICROSOFT,
|
OPT_TRANS_MICROSOFT,
|
||||||
|
OPT_TRANS_BAIDU,
|
||||||
|
OPT_TRANS_TENCENT,
|
||||||
OPT_TRANS_DEEPL,
|
OPT_TRANS_DEEPL,
|
||||||
OPT_TRANS_DEEPLFREE,
|
OPT_TRANS_DEEPLFREE,
|
||||||
OPT_TRANS_DEEPLX,
|
OPT_TRANS_DEEPLX,
|
||||||
OPT_TRANS_BAIDU,
|
OPT_TRANS_NIUTRANS,
|
||||||
OPT_TRANS_TENCENT,
|
|
||||||
OPT_TRANS_OPENAI,
|
OPT_TRANS_OPENAI,
|
||||||
|
OPT_TRANS_GEMINI,
|
||||||
OPT_TRANS_CLOUDFLAREAI,
|
OPT_TRANS_CLOUDFLAREAI,
|
||||||
OPT_TRANS_CUSTOMIZE,
|
OPT_TRANS_CUSTOMIZE,
|
||||||
|
OPT_TRANS_CUSTOMIZE_2,
|
||||||
|
OPT_TRANS_CUSTOMIZE_3,
|
||||||
|
OPT_TRANS_CUSTOMIZE_4,
|
||||||
|
OPT_TRANS_CUSTOMIZE_5,
|
||||||
];
|
];
|
||||||
|
|
||||||
export const OPT_LANGS_TO = [
|
export const OPT_LANGS_TO = [
|
||||||
@@ -172,6 +197,12 @@ export const OPT_LANGS_SPECIAL = {
|
|||||||
["zh-CN", "ZH"],
|
["zh-CN", "ZH"],
|
||||||
["zh-TW", "ZH"],
|
["zh-TW", "ZH"],
|
||||||
]),
|
]),
|
||||||
|
[OPT_TRANS_NIUTRANS]: new Map([
|
||||||
|
...OPT_LANGS_FROM.map(([key]) => [key, key]),
|
||||||
|
["auto", "auto"],
|
||||||
|
["zh-CN", "zh"],
|
||||||
|
["zh-TW", "cht"],
|
||||||
|
]),
|
||||||
[OPT_TRANS_BAIDU]: new Map([
|
[OPT_TRANS_BAIDU]: new Map([
|
||||||
...OPT_LANGS_FROM.map(([key]) => [key, key]),
|
...OPT_LANGS_FROM.map(([key]) => [key, key]),
|
||||||
["zh-CN", "zh"],
|
["zh-CN", "zh"],
|
||||||
@@ -224,6 +255,9 @@ export const OPT_LANGS_SPECIAL = {
|
|||||||
[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_GEMINI]: new Map(
|
||||||
|
OPT_LANGS_FROM.map(([key, val]) => [key, val.split(" - ")[0]])
|
||||||
|
),
|
||||||
[OPT_TRANS_CLOUDFLAREAI]: new Map([
|
[OPT_TRANS_CLOUDFLAREAI]: new Map([
|
||||||
["auto", ""],
|
["auto", ""],
|
||||||
["zh-CN", "chinese"],
|
["zh-CN", "chinese"],
|
||||||
@@ -242,6 +276,22 @@ export const OPT_LANGS_SPECIAL = {
|
|||||||
...OPT_LANGS_FROM.map(([key]) => [key, key]),
|
...OPT_LANGS_FROM.map(([key]) => [key, key]),
|
||||||
["auto", ""],
|
["auto", ""],
|
||||||
]),
|
]),
|
||||||
|
[OPT_TRANS_CUSTOMIZE_2]: new Map([
|
||||||
|
...OPT_LANGS_FROM.map(([key]) => [key, key]),
|
||||||
|
["auto", ""],
|
||||||
|
]),
|
||||||
|
[OPT_TRANS_CUSTOMIZE_3]: new Map([
|
||||||
|
...OPT_LANGS_FROM.map(([key]) => [key, key]),
|
||||||
|
["auto", ""],
|
||||||
|
]),
|
||||||
|
[OPT_TRANS_CUSTOMIZE_4]: new Map([
|
||||||
|
...OPT_LANGS_FROM.map(([key]) => [key, key]),
|
||||||
|
["auto", ""],
|
||||||
|
]),
|
||||||
|
[OPT_TRANS_CUSTOMIZE_5]: new Map([
|
||||||
|
...OPT_LANGS_FROM.map(([key]) => [key, key]),
|
||||||
|
["auto", ""],
|
||||||
|
]),
|
||||||
};
|
};
|
||||||
export const OPT_LANGS_LIST = OPT_LANGS_TO.map(([lang]) => lang);
|
export const OPT_LANGS_LIST = OPT_LANGS_TO.map(([lang]) => lang);
|
||||||
export const OPT_LANGS_BAIDU = new Map(
|
export const OPT_LANGS_BAIDU = new Map(
|
||||||
@@ -265,6 +315,7 @@ export const OPT_STYLE_DASHLINE = "dash_line"; // 虚线
|
|||||||
export const OPT_STYLE_WAVYLINE = "wavy_line"; // 波浪线
|
export const OPT_STYLE_WAVYLINE = "wavy_line"; // 波浪线
|
||||||
export const OPT_STYLE_FUZZY = "fuzzy"; // 模糊
|
export const OPT_STYLE_FUZZY = "fuzzy"; // 模糊
|
||||||
export const OPT_STYLE_HIGHLIGHT = "highlight"; // 高亮
|
export const OPT_STYLE_HIGHLIGHT = "highlight"; // 高亮
|
||||||
|
export const OPT_STYLE_BLOCKQUOTE = "blockquote"; // 引用
|
||||||
export const OPT_STYLE_DIY = "diy_style"; // 自定义样式
|
export const OPT_STYLE_DIY = "diy_style"; // 自定义样式
|
||||||
export const OPT_STYLE_ALL = [
|
export const OPT_STYLE_ALL = [
|
||||||
OPT_STYLE_NONE,
|
OPT_STYLE_NONE,
|
||||||
@@ -274,6 +325,7 @@ export const OPT_STYLE_ALL = [
|
|||||||
OPT_STYLE_WAVYLINE,
|
OPT_STYLE_WAVYLINE,
|
||||||
OPT_STYLE_FUZZY,
|
OPT_STYLE_FUZZY,
|
||||||
OPT_STYLE_HIGHLIGHT,
|
OPT_STYLE_HIGHLIGHT,
|
||||||
|
OPT_STYLE_BLOCKQUOTE,
|
||||||
OPT_STYLE_DIY,
|
OPT_STYLE_DIY,
|
||||||
];
|
];
|
||||||
export const OPT_STYLE_USE_COLOR = [
|
export const OPT_STYLE_USE_COLOR = [
|
||||||
@@ -282,40 +334,64 @@ export const OPT_STYLE_USE_COLOR = [
|
|||||||
OPT_STYLE_DASHLINE,
|
OPT_STYLE_DASHLINE,
|
||||||
OPT_STYLE_WAVYLINE,
|
OPT_STYLE_WAVYLINE,
|
||||||
OPT_STYLE_HIGHLIGHT,
|
OPT_STYLE_HIGHLIGHT,
|
||||||
|
OPT_STYLE_BLOCKQUOTE,
|
||||||
];
|
];
|
||||||
|
|
||||||
export const OPT_MOUSEKEY_DISABLE = "mk_disable";
|
export const OPT_TIMING_PAGESCROLL = "mk_pagescroll"; // 滚动加载翻译
|
||||||
export const OPT_MOUSEKEY_MOUSEOVER = "mk_mouseover";
|
export const OPT_TIMING_PAGEOPEN = "mk_pageopen"; // 直接翻译到底
|
||||||
export const OPT_MOUSEKEY_CONTROL = "mk_ctrlKey";
|
export const OPT_TIMING_MOUSEOVER = "mk_mouseover";
|
||||||
export const OPT_MOUSEKEY_SHIFT = "mk_shiftKey";
|
export const OPT_TIMING_CONTROL = "mk_ctrlKey";
|
||||||
export const OPT_MOUSEKEY_ALT = "mk_altKey";
|
export const OPT_TIMING_SHIFT = "mk_shiftKey";
|
||||||
export const OPT_MOUSEKEY_ALL = [
|
export const OPT_TIMING_ALT = "mk_altKey";
|
||||||
OPT_MOUSEKEY_DISABLE,
|
export const OPT_TIMING_ALL = [
|
||||||
OPT_MOUSEKEY_MOUSEOVER,
|
OPT_TIMING_PAGESCROLL,
|
||||||
OPT_MOUSEKEY_CONTROL,
|
OPT_TIMING_PAGEOPEN,
|
||||||
OPT_MOUSEKEY_SHIFT,
|
OPT_TIMING_MOUSEOVER,
|
||||||
OPT_MOUSEKEY_ALT,
|
OPT_TIMING_CONTROL,
|
||||||
|
OPT_TIMING_SHIFT,
|
||||||
|
OPT_TIMING_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; // 默认任务间隔时间
|
||||||
|
|
||||||
export const PROMPT_PLACE_FROM = "{{from}}"; // 占位符
|
export const INPUT_PLACE_URL = "{{url}}"; // 占位符
|
||||||
export const PROMPT_PLACE_TO = "{{to}}"; // 占位符
|
export const INPUT_PLACE_FROM = "{{from}}"; // 占位符
|
||||||
|
export const INPUT_PLACE_TO = "{{to}}"; // 占位符
|
||||||
|
export const INPUT_PLACE_TEXT = "{{text}}"; // 占位符
|
||||||
|
export const INPUT_PLACE_KEY = "{{key}}"; // 占位符
|
||||||
|
|
||||||
export const DEFAULT_COLOR = "#209CEE"; // 默认高亮背景色/线条颜色
|
export const DEFAULT_COLOR = "#209CEE"; // 默认高亮背景色/线条颜色
|
||||||
|
|
||||||
|
export const DEFAULT_TRANS_TAG = "span";
|
||||||
|
export const DEFAULT_SELECT_STYLE =
|
||||||
|
"-webkit-line-clamp: unset; max-height: none; height: auto;";
|
||||||
|
|
||||||
// 全局规则
|
// 全局规则
|
||||||
export const GLOBLA_RULE = {
|
export const GLOBLA_RULE = {
|
||||||
pattern: "*",
|
pattern: "*", // 匹配网址
|
||||||
selector: DEFAULT_SELECTOR,
|
selector: DEFAULT_SELECTOR, // 选择器
|
||||||
translator: OPT_TRANS_MICROSOFT,
|
keepSelector: DEFAULT_KEEP_SELECTOR, // 保留元素选择器
|
||||||
fromLang: "auto",
|
terms: "", // 专业术语
|
||||||
toLang: "zh-CN",
|
translator: OPT_TRANS_MICROSOFT, // 翻译服务
|
||||||
textStyle: OPT_STYLE_DASHLINE,
|
fromLang: "auto", // 源语言
|
||||||
transOpen: "false",
|
toLang: "zh-CN", // 目标语言
|
||||||
bgColor: "",
|
textStyle: OPT_STYLE_DASHLINE, // 译文样式
|
||||||
textDiyStyle: "",
|
transOpen: "false", // 开启翻译
|
||||||
|
bgColor: "", // 译文颜色
|
||||||
|
textDiyStyle: "", // 自定义译文样式
|
||||||
|
selectStyle: DEFAULT_SELECT_STYLE, // 选择器节点样式
|
||||||
|
parentStyle: DEFAULT_SELECT_STYLE, // 选择器父节点样式
|
||||||
|
injectJs: "", // 注入JS
|
||||||
|
injectCss: "", // 注入CSS
|
||||||
|
transOnly: "false", // 是否仅显示译文
|
||||||
|
transTiming: OPT_TIMING_PAGESCROLL, // 翻译时机/鼠标悬停翻译
|
||||||
|
transTag: DEFAULT_TRANS_TAG, // 译文元素标签
|
||||||
|
transTitle: "false", // 是否同时翻译页面标题
|
||||||
|
detectRemote: "false", // 是否使用远程语言检测
|
||||||
|
skipLangs: [], // 不翻译的语言
|
||||||
|
fixerSelector: "", // 修复函数选择器
|
||||||
|
fixerFunc: "-", // 修复函数
|
||||||
};
|
};
|
||||||
|
|
||||||
// 输入框翻译
|
// 输入框翻译
|
||||||
@@ -333,15 +409,33 @@ export const DEFAULT_INPUT_RULE = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 划词翻译
|
// 划词翻译
|
||||||
export const DEFAULT_TRANBOX_SHORTCUT = ["AltLeft", "KeyB"];
|
export const PHONIC_MAP = {
|
||||||
|
en_phonic: ["英", "uk"],
|
||||||
|
us_phonic: ["美", "en"],
|
||||||
|
};
|
||||||
|
export const OPT_TRANBOX_TRIGGER_CLICK = "click";
|
||||||
|
export const OPT_TRANBOX_TRIGGER_HOVER = "hover";
|
||||||
|
export const OPT_TRANBOX_TRIGGER_SELECT = "select";
|
||||||
|
export const OPT_TRANBOX_TRIGGER_ALL = [
|
||||||
|
OPT_TRANBOX_TRIGGER_CLICK,
|
||||||
|
OPT_TRANBOX_TRIGGER_HOVER,
|
||||||
|
OPT_TRANBOX_TRIGGER_SELECT,
|
||||||
|
];
|
||||||
|
export const DEFAULT_TRANBOX_SHORTCUT = ["AltLeft", "KeyS"];
|
||||||
export const DEFAULT_TRANBOX_SETTING = {
|
export const DEFAULT_TRANBOX_SETTING = {
|
||||||
transOpen: true,
|
transOpen: true,
|
||||||
translator: OPT_TRANS_MICROSOFT,
|
translator: OPT_TRANS_MICROSOFT,
|
||||||
fromLang: "auto",
|
fromLang: "auto",
|
||||||
toLang: "zh-CN",
|
toLang: "zh-CN",
|
||||||
|
toLang2: "en",
|
||||||
tranboxShortcut: DEFAULT_TRANBOX_SHORTCUT,
|
tranboxShortcut: DEFAULT_TRANBOX_SHORTCUT,
|
||||||
btnOffsetX: 10,
|
btnOffsetX: 10,
|
||||||
btnOffsetY: 10,
|
btnOffsetY: 10,
|
||||||
|
hideTranBtn: false, // 是否隐藏翻译按钮
|
||||||
|
hideClickAway: false, // 是否点击外部关闭弹窗
|
||||||
|
simpleStyle: false, // 是否简洁界面
|
||||||
|
triggerMode: OPT_TRANBOX_TRIGGER_CLICK, // 触发翻译方式
|
||||||
|
extStyles: "", // 附加样式
|
||||||
};
|
};
|
||||||
|
|
||||||
// 订阅列表
|
// 订阅列表
|
||||||
@@ -361,33 +455,83 @@ export const DEFAULT_SUBRULES_LIST = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
// 翻译接口
|
// 翻译接口
|
||||||
|
const defaultCustomApi = {
|
||||||
|
url: "",
|
||||||
|
key: "",
|
||||||
|
customOption: "",
|
||||||
|
fetchLimit: DEFAULT_FETCH_LIMIT,
|
||||||
|
fetchInterval: DEFAULT_FETCH_INTERVAL,
|
||||||
|
};
|
||||||
export const DEFAULT_TRANS_APIS = {
|
export const DEFAULT_TRANS_APIS = {
|
||||||
[OPT_TRANS_GOOGLE]: {
|
[OPT_TRANS_GOOGLE]: {
|
||||||
url: "https://translate.googleapis.com/translate_a/single",
|
url: "https://translate.googleapis.com/translate_a/single",
|
||||||
key: "",
|
key: "",
|
||||||
|
fetchLimit: DEFAULT_FETCH_LIMIT, // 最大任务数量
|
||||||
|
fetchInterval: DEFAULT_FETCH_INTERVAL, // 任务间隔时间
|
||||||
|
},
|
||||||
|
[OPT_TRANS_MICROSOFT]: {
|
||||||
|
fetchLimit: DEFAULT_FETCH_LIMIT,
|
||||||
|
fetchInterval: DEFAULT_FETCH_INTERVAL,
|
||||||
|
},
|
||||||
|
[OPT_TRANS_BAIDU]: {
|
||||||
|
fetchLimit: DEFAULT_FETCH_LIMIT,
|
||||||
|
fetchInterval: DEFAULT_FETCH_INTERVAL,
|
||||||
|
},
|
||||||
|
[OPT_TRANS_TENCENT]: {
|
||||||
|
fetchLimit: DEFAULT_FETCH_LIMIT,
|
||||||
|
fetchInterval: DEFAULT_FETCH_INTERVAL,
|
||||||
},
|
},
|
||||||
[OPT_TRANS_DEEPL]: {
|
[OPT_TRANS_DEEPL]: {
|
||||||
url: "https://api-free.deepl.com/v2/translate",
|
url: "https://api-free.deepl.com/v2/translate",
|
||||||
key: "",
|
key: "",
|
||||||
|
fetchLimit: 1,
|
||||||
|
fetchInterval: 500,
|
||||||
|
},
|
||||||
|
[OPT_TRANS_DEEPLFREE]: {
|
||||||
|
fetchLimit: 1,
|
||||||
|
fetchInterval: 500,
|
||||||
},
|
},
|
||||||
[OPT_TRANS_DEEPLX]: {
|
[OPT_TRANS_DEEPLX]: {
|
||||||
url: "http://localhost:1188/translate",
|
url: "http://localhost:1188/translate",
|
||||||
key: "",
|
key: "",
|
||||||
|
fetchLimit: 1,
|
||||||
|
fetchInterval: 500,
|
||||||
|
},
|
||||||
|
[OPT_TRANS_NIUTRANS]: {
|
||||||
|
url: "https://api.niutrans.com/NiuTransServer/translation",
|
||||||
|
key: "",
|
||||||
|
dictNo: "",
|
||||||
|
memoryNo: "",
|
||||||
|
fetchLimit: DEFAULT_FETCH_LIMIT,
|
||||||
|
fetchInterval: DEFAULT_FETCH_INTERVAL,
|
||||||
},
|
},
|
||||||
[OPT_TRANS_OPENAI]: {
|
[OPT_TRANS_OPENAI]: {
|
||||||
url: "https://api.openai.com/v1/chat/completions",
|
url: "https://api.openai.com/v1/chat/completions",
|
||||||
key: "",
|
key: "",
|
||||||
model: "gpt-4",
|
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}.`,
|
prompt: `You will be provided with a sentence in ${INPUT_PLACE_FROM}, and your task is to translate it into ${INPUT_PLACE_TO}.`,
|
||||||
|
fetchLimit: 1,
|
||||||
|
fetchInterval: 500,
|
||||||
|
},
|
||||||
|
[OPT_TRANS_GEMINI]: {
|
||||||
|
url: "https://generativelanguage.googleapis.com/v1/models",
|
||||||
|
key: "",
|
||||||
|
model: "gemini-pro",
|
||||||
|
prompt: `Translate the following text from ${INPUT_PLACE_FROM} to ${INPUT_PLACE_TO}:\n\n${INPUT_PLACE_TEXT}`,
|
||||||
|
fetchLimit: 1,
|
||||||
|
fetchInterval: 500,
|
||||||
},
|
},
|
||||||
[OPT_TRANS_CLOUDFLAREAI]: {
|
[OPT_TRANS_CLOUDFLAREAI]: {
|
||||||
url: "https://api.cloudflare.com/client/v4/accounts/{ACCOUNT_ID}/ai/run/@cf/meta/m2m100-1.2b",
|
url: "https://api.cloudflare.com/client/v4/accounts/{ACCOUNT_ID}/ai/run/@cf/meta/m2m100-1.2b",
|
||||||
key: "",
|
key: "",
|
||||||
|
fetchLimit: 1,
|
||||||
|
fetchInterval: 500,
|
||||||
},
|
},
|
||||||
[OPT_TRANS_CUSTOMIZE]: {
|
[OPT_TRANS_CUSTOMIZE]: defaultCustomApi,
|
||||||
url: "",
|
[OPT_TRANS_CUSTOMIZE_2]: defaultCustomApi,
|
||||||
key: "",
|
[OPT_TRANS_CUSTOMIZE_3]: defaultCustomApi,
|
||||||
},
|
[OPT_TRANS_CUSTOMIZE_4]: defaultCustomApi,
|
||||||
|
[OPT_TRANS_CUSTOMIZE_5]: defaultCustomApi,
|
||||||
};
|
};
|
||||||
|
|
||||||
// 默认快捷键
|
// 默认快捷键
|
||||||
@@ -399,33 +543,50 @@ export const DEFAULT_SHORTCUTS = {
|
|||||||
[OPT_SHORTCUT_TRANSLATE]: ["AltLeft", "KeyQ"],
|
[OPT_SHORTCUT_TRANSLATE]: ["AltLeft", "KeyQ"],
|
||||||
[OPT_SHORTCUT_STYLE]: ["AltLeft", "KeyC"],
|
[OPT_SHORTCUT_STYLE]: ["AltLeft", "KeyC"],
|
||||||
[OPT_SHORTCUT_POPUP]: ["AltLeft", "KeyK"],
|
[OPT_SHORTCUT_POPUP]: ["AltLeft", "KeyK"],
|
||||||
[OPT_SHORTCUT_SETTING]: ["AltLeft", "KeyN"],
|
[OPT_SHORTCUT_SETTING]: ["AltLeft", "KeyO"],
|
||||||
};
|
};
|
||||||
|
|
||||||
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 = 20; // 换行字符数
|
export const TRANS_NEWLINE_LENGTH = 20; // 换行字符数
|
||||||
|
export const DEFAULT_BLACKLIST = [
|
||||||
|
"https://fishjar.github.io/kiss-translator/options.html",
|
||||||
|
"https://translate.google.com",
|
||||||
|
"https://www.deepl.com/translator",
|
||||||
|
"oapi.dingtalk.com",
|
||||||
|
"login.dingtalk.com",
|
||||||
|
]; // 禁用翻译名单
|
||||||
|
export const DEFAULT_CSPLIST = ["https://github.com"]; // 禁用CSP名单
|
||||||
|
|
||||||
export const DEFAULT_SETTING = {
|
export const DEFAULT_SETTING = {
|
||||||
darkMode: false, // 深色模式
|
darkMode: false, // 深色模式
|
||||||
uiLang: "en", // 界面语言
|
uiLang: "en", // 界面语言
|
||||||
fetchLimit: DEFAULT_FETCH_LIMIT, // 最大任务数量
|
// fetchLimit: DEFAULT_FETCH_LIMIT, // 最大任务数量(移至transApis,作废)
|
||||||
fetchInterval: DEFAULT_FETCH_INTERVAL, // 任务间隔时间
|
// fetchInterval: DEFAULT_FETCH_INTERVAL, // 任务间隔时间(移至transApis,作废)
|
||||||
minLength: TRANS_MIN_LENGTH,
|
minLength: TRANS_MIN_LENGTH,
|
||||||
maxLength: TRANS_MAX_LENGTH,
|
maxLength: TRANS_MAX_LENGTH,
|
||||||
newlineLength: TRANS_NEWLINE_LENGTH,
|
newlineLength: TRANS_NEWLINE_LENGTH,
|
||||||
clearCache: false, // 是否在浏览器下次启动时清除缓存
|
clearCache: false, // 是否在浏览器下次启动时清除缓存
|
||||||
injectRules: true, // 是否注入订阅规则
|
injectRules: true, // 是否注入订阅规则
|
||||||
injectWebfix: true, // 是否注入修复补丁
|
// injectWebfix: true, // 是否注入修复补丁(作废)
|
||||||
detectRemote: false, // 是否使用远程语言检测
|
// detectRemote: false, // 是否使用远程语言检测(移至rule,作废)
|
||||||
|
// contextMenus: true, // 是否添加右键菜单(作废)
|
||||||
|
contextMenuType: 1, // 右键菜单类型(0不显示,1简单菜单,2多级菜单)
|
||||||
|
// transTag: DEFAULT_TRANS_TAG, // 译文元素标签(移至rule,作废)
|
||||||
|
// transOnly: false, // 是否仅显示译文(移至rule,作废)
|
||||||
|
// transTitle: false, // 是否同时翻译页面标题(移至rule,作废)
|
||||||
subrulesList: DEFAULT_SUBRULES_LIST, // 订阅列表
|
subrulesList: DEFAULT_SUBRULES_LIST, // 订阅列表
|
||||||
owSubrule: DEFAULT_OW_RULE, // 覆写订阅规则
|
owSubrule: DEFAULT_OW_RULE, // 覆写订阅规则
|
||||||
transApis: DEFAULT_TRANS_APIS, // 翻译接口
|
transApis: DEFAULT_TRANS_APIS, // 翻译接口
|
||||||
mouseKey: OPT_MOUSEKEY_DISABLE, // 鼠标悬停翻译
|
// mouseKey: OPT_TIMING_PAGESCROLL, // 翻译时机/鼠标悬停翻译(移至rule,作废)
|
||||||
shortcuts: DEFAULT_SHORTCUTS, // 快捷键
|
shortcuts: DEFAULT_SHORTCUTS, // 快捷键
|
||||||
inputRule: DEFAULT_INPUT_RULE, // 输入框设置
|
inputRule: DEFAULT_INPUT_RULE, // 输入框设置
|
||||||
tranboxSetting: DEFAULT_TRANBOX_SETTING, // 划词翻译设置
|
tranboxSetting: DEFAULT_TRANBOX_SETTING, // 划词翻译设置
|
||||||
touchTranslate: 2, // 触屏翻译
|
touchTranslate: 2, // 触屏翻译
|
||||||
|
blacklist: DEFAULT_BLACKLIST.join(",\n"), // 禁用翻译名单
|
||||||
|
csplist: DEFAULT_CSPLIST.join(",\n"), // 禁用CSP名单
|
||||||
|
// disableLangs: [], // 不翻译的语言(移至rule,作废)
|
||||||
|
transInterval: 500, // 翻译间隔时间
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DEFAULT_RULES = [GLOBLA_RULE];
|
export const DEFAULT_RULES = [GLOBLA_RULE];
|
||||||
|
|||||||
@@ -1,22 +1,35 @@
|
|||||||
const els = `li, p, h1, h2, h3, h4, h5, h6, dd, blockquote`;
|
import { FIXER_BR, FIXER_BN, FIXER_BR_DIV, FIXER_BN_DIV } from "../libs/webfix";
|
||||||
|
|
||||||
export const DEFAULT_SELECTOR = `:is(${els})`;
|
|
||||||
|
|
||||||
export const GLOBAL_KEY = "*";
|
export const GLOBAL_KEY = "*";
|
||||||
export const REMAIN_KEY = "-";
|
export const REMAIN_KEY = "-";
|
||||||
|
|
||||||
export const SHADOW_KEY = ">>>";
|
export const SHADOW_KEY = ">>>";
|
||||||
|
|
||||||
|
export const DEFAULT_SELECTOR = `:is(li, p, h1, h2, h3, h4, h5, h6, dd, blockquote, .kiss-p)`;
|
||||||
|
export const DEFAULT_KEEP_SELECTOR = `code, img, svg, pre`;
|
||||||
export const DEFAULT_RULE = {
|
export const DEFAULT_RULE = {
|
||||||
pattern: "",
|
pattern: "", // 匹配网址
|
||||||
selector: "",
|
selector: "", // 选择器
|
||||||
translator: GLOBAL_KEY,
|
keepSelector: "", // 保留元素选择器
|
||||||
fromLang: GLOBAL_KEY,
|
terms: "", // 专业术语
|
||||||
toLang: GLOBAL_KEY,
|
translator: GLOBAL_KEY, // 翻译服务
|
||||||
textStyle: GLOBAL_KEY,
|
fromLang: GLOBAL_KEY, // 源语言
|
||||||
transOpen: GLOBAL_KEY,
|
toLang: GLOBAL_KEY, // 目标语言
|
||||||
bgColor: "",
|
textStyle: GLOBAL_KEY, // 译文样式
|
||||||
textDiyStyle: "",
|
transOpen: GLOBAL_KEY, // 开启翻译
|
||||||
|
bgColor: "", // 译文颜色
|
||||||
|
textDiyStyle: "", // 自定义译文样式
|
||||||
|
selectStyle: "", // 选择器节点样式
|
||||||
|
parentStyle: "", // 选择器父节点样式
|
||||||
|
injectJs: "", // 注入JS
|
||||||
|
injectCss: "", // 注入CSS
|
||||||
|
transOnly: GLOBAL_KEY, // 是否仅显示译文
|
||||||
|
transTiming: GLOBAL_KEY, // 翻译时机/鼠标悬停翻译
|
||||||
|
transTag: GLOBAL_KEY, // 译文元素标签
|
||||||
|
transTitle: GLOBAL_KEY, // 是否同时翻译页面标题
|
||||||
|
detectRemote: GLOBAL_KEY, // 是否使用远程语言检测
|
||||||
|
skipLangs: [], // 不翻译的语言
|
||||||
|
fixerSelector: "", // 修复函数选择器
|
||||||
|
fixerFunc: GLOBAL_KEY, // 修复函数
|
||||||
};
|
};
|
||||||
|
|
||||||
const DEFAULT_DIY_STYLE = `color: #666;
|
const DEFAULT_DIY_STYLE = `color: #666;
|
||||||
@@ -42,145 +55,264 @@ export const DEFAULT_OW_RULE = {
|
|||||||
textDiyStyle: DEFAULT_DIY_STYLE,
|
textDiyStyle: DEFAULT_DIY_STYLE,
|
||||||
};
|
};
|
||||||
|
|
||||||
const RULES = [
|
const RULES_MAP = {
|
||||||
{
|
"www.google.com/search": {
|
||||||
pattern: `www.google.com/search`,
|
|
||||||
selector: `h3, .IsZvec, .VwiC3b`,
|
selector: `h3, .IsZvec, .VwiC3b`,
|
||||||
},
|
},
|
||||||
{
|
"news.google.com": {
|
||||||
pattern: `news.google.com`,
|
selector: `[data-n-tid], ${DEFAULT_SELECTOR}`,
|
||||||
selector: `h4`,
|
|
||||||
},
|
},
|
||||||
{
|
"www.foxnews.com": {
|
||||||
pattern: `www.foxnews.com`,
|
|
||||||
selector: `h1, h2, .title, .sidebar [data-type="Title"], .article-content ${DEFAULT_SELECTOR}; [data-spotim-module="conversation"]>div >>> [data-spot-im-class="message-text"] p, [data-spot-im-class="message-text"]`,
|
selector: `h1, h2, .title, .sidebar [data-type="Title"], .article-content ${DEFAULT_SELECTOR}; [data-spotim-module="conversation"]>div >>> [data-spot-im-class="message-text"] p, [data-spot-im-class="message-text"]`,
|
||||||
},
|
},
|
||||||
{
|
"bearblog.dev, www.theverge.com, www.tampermonkey.net/documentation.php": {
|
||||||
pattern: `bearblog.dev, www.theverge.com, www.tampermonkey.net/documentation.php`,
|
selector: `${DEFAULT_SELECTOR}`,
|
||||||
selector: DEFAULT_SELECTOR,
|
|
||||||
},
|
},
|
||||||
{
|
"themessenger.com": {
|
||||||
pattern: `themessenger.com`,
|
|
||||||
selector: `.leading-tight, .leading-tighter, .my-2 p, .font-body p, article ${DEFAULT_SELECTOR}`,
|
selector: `.leading-tight, .leading-tighter, .my-2 p, .font-body p, article ${DEFAULT_SELECTOR}`,
|
||||||
},
|
},
|
||||||
{
|
"www.telegraph.co.uk, go.dev/doc/": {
|
||||||
pattern: `www.telegraph.co.uk`,
|
|
||||||
selector: `article ${DEFAULT_SELECTOR}`,
|
selector: `article ${DEFAULT_SELECTOR}`,
|
||||||
},
|
},
|
||||||
{
|
"www.theguardian.com": {
|
||||||
pattern: `www.theguardian.com`,
|
|
||||||
selector: `.show-underline, .dcr-hup5wm div, .dcr-7vl6y8 div, .dcr-12evv1c, figcaption, article ${DEFAULT_SELECTOR}, [data-cy="mostviewed-footer"] h4`,
|
selector: `.show-underline, .dcr-hup5wm div, .dcr-7vl6y8 div, .dcr-12evv1c, figcaption, article ${DEFAULT_SELECTOR}, [data-cy="mostviewed-footer"] h4`,
|
||||||
},
|
},
|
||||||
{
|
"www.semafor.com": {
|
||||||
pattern: `www.semafor.com`,
|
|
||||||
selector: `${DEFAULT_SELECTOR}, .styles_intro__IYj__, [class*="styles_description"]`,
|
selector: `${DEFAULT_SELECTOR}, .styles_intro__IYj__, [class*="styles_description"]`,
|
||||||
},
|
},
|
||||||
{
|
"www.noemamag.com": {
|
||||||
pattern: `www.noemamag.com`,
|
|
||||||
selector: `.splash__title, .single-card__title, .single-card__type, .single-card__topic, .highlighted-content__title, .single-card__author, article ${DEFAULT_SELECTOR}, .quote__text, .wp-caption-text div`,
|
selector: `.splash__title, .single-card__title, .single-card__type, .single-card__topic, .highlighted-content__title, .single-card__author, article ${DEFAULT_SELECTOR}, .quote__text, .wp-caption-text div`,
|
||||||
},
|
},
|
||||||
{
|
"restofworld.org": {
|
||||||
pattern: `restofworld.org`,
|
|
||||||
selector: `${DEFAULT_SELECTOR}, .recirc-story__headline, .recirc-story__dek`,
|
selector: `${DEFAULT_SELECTOR}, .recirc-story__headline, .recirc-story__dek`,
|
||||||
},
|
},
|
||||||
{
|
"www.axios.com": {
|
||||||
pattern: `www.axios.com`,
|
|
||||||
selector: `.h7, ${DEFAULT_SELECTOR}`,
|
selector: `.h7, ${DEFAULT_SELECTOR}`,
|
||||||
},
|
},
|
||||||
{
|
"www.newyorker.com": {
|
||||||
pattern: `www.newyorker.com`,
|
selector: `.summary-item__hed, .summary-item__dek, .summary-collection-grid__dek, .dqtvfu, .rubric__link, .caption, article ${DEFAULT_SELECTOR}, .HEhan ${DEFAULT_SELECTOR}, .ContributorBioBio-fBolsO, .BaseText-ewhhUZ`,
|
||||||
selector: `.summary-item__hed, .summary-item__dek, .summary-collection-grid__dek, .dqtvfu, .rubric__link, .caption, article ${DEFAULT_SELECTOR}, .HEhan ${DEFAULT_SELECTOR}, .ContributorBioBio-fBolsO`,
|
|
||||||
},
|
},
|
||||||
{
|
"time.com": {
|
||||||
pattern: `https://time.com/`,
|
|
||||||
selector: `h1, h3, .summary, .video-title, #article-body ${DEFAULT_SELECTOR}, .image-wrap-container .credit.body-caption, .media-heading`,
|
selector: `h1, h3, .summary, .video-title, #article-body ${DEFAULT_SELECTOR}, .image-wrap-container .credit.body-caption, .media-heading`,
|
||||||
},
|
},
|
||||||
{
|
"www.dw.com": {
|
||||||
pattern: `www.dw.com`,
|
|
||||||
selector: `.ts-teaser-title a, .news-title a, .title a, .teaser-description a, .hbudab h3, .hbudab p, figcaption ,article ${DEFAULT_SELECTOR}`,
|
selector: `.ts-teaser-title a, .news-title a, .title a, .teaser-description a, .hbudab h3, .hbudab p, figcaption ,article ${DEFAULT_SELECTOR}`,
|
||||||
},
|
},
|
||||||
{
|
"www.bbc.com": {
|
||||||
pattern: `www.bbc.com`,
|
selector: `h1, h2, .media__link, .media__summary, article ${DEFAULT_SELECTOR}, .ssrcss-y7krbn-Stack, .ssrcss-17zglt8-PromoHeadline, .ssrcss-18cjaf3-Headline, .gs-c-promo-heading__title, .gs-c-promo-summary, .media__content h3, .article__intro, .lx-c-summary-points>li`,
|
||||||
selector: `h1, h2, .media__link, .media__summary, article ${DEFAULT_SELECTOR}, .ssrcss-y7krbn-Stack, .ssrcss-1mrs5ns-PromoLink, .ssrcss-18cjaf3-Headline, .gs-c-promo-heading__title, .gs-c-promo-summary, .media__content h3, .article__intro`,
|
|
||||||
},
|
},
|
||||||
{
|
"www.chinadaily.com.cn": {
|
||||||
pattern: `www.chinadaily.com.cn`,
|
|
||||||
selector: `h1, .tMain [shape="rect"], .cMain [shape="rect"], .photo_art [shape="rect"], .mai_r [shape="rect"], .lisBox li, #Content ${DEFAULT_SELECTOR}`,
|
selector: `h1, .tMain [shape="rect"], .cMain [shape="rect"], .photo_art [shape="rect"], .mai_r [shape="rect"], .lisBox li, #Content ${DEFAULT_SELECTOR}`,
|
||||||
},
|
},
|
||||||
{
|
"www.facebook.com": {
|
||||||
pattern: `www.facebook.com`,
|
|
||||||
selector: `[role="main"] [dir="auto"]`,
|
selector: `[role="main"] [dir="auto"]`,
|
||||||
},
|
},
|
||||||
{
|
"www.reddit.com, new.reddit.com, sh.reddit.com": {
|
||||||
pattern: `www.reddit.com`,
|
selector: `:is(#AppRouter-main-content, #overlayScrollContainer) :is([class^=tbIA],[class^=_1zP],[class^=ULWj],[class^=_2Jj], [class^=_334],[class^=_2Gr],[class^=_7T4],[class^=_1WO], ${DEFAULT_SELECTOR}); [id^="post-title"], :is([slot="text-body"], [slot="comment"]) ${DEFAULT_SELECTOR}, recent-posts h3, aside :is(span:has(>h2), p); shreddit-subreddit-header >>> :is(#title, #description)`,
|
||||||
selector: `[slot="title"], [slot="text-body"] ${DEFAULT_SELECTOR}, #-post-rtjson-content p`,
|
|
||||||
},
|
},
|
||||||
{
|
"www.quora.com": {
|
||||||
pattern: `www.quora.com`,
|
|
||||||
selector: `.qu-wordBreak--break-word`,
|
selector: `.qu-wordBreak--break-word`,
|
||||||
},
|
},
|
||||||
{
|
"edition.cnn.com": {
|
||||||
pattern: `edition.cnn.com`,
|
|
||||||
selector: `.container__title, .container__headline, .headline__text, .image__caption, [data-type="Title"], .article__content ${DEFAULT_SELECTOR}`,
|
selector: `.container__title, .container__headline, .headline__text, .image__caption, [data-type="Title"], .article__content ${DEFAULT_SELECTOR}`,
|
||||||
},
|
},
|
||||||
{
|
"www.reuters.com": {
|
||||||
pattern: `www.reuters.com`,
|
|
||||||
selector: `#main-content [data-testid="Heading"], #main-content [data-testid="Body"], .article-body__content__17Yit ${DEFAULT_SELECTOR}`,
|
selector: `#main-content [data-testid="Heading"], #main-content [data-testid="Body"], .article-body__content__17Yit ${DEFAULT_SELECTOR}`,
|
||||||
},
|
},
|
||||||
{
|
"www.bloomberg.com": {
|
||||||
pattern: `www.bloomberg.com`,
|
|
||||||
selector: `[data-component="headline"], [data-component="related-item-headline"], [data-component="title"], article ${DEFAULT_SELECTOR}`,
|
selector: `[data-component="headline"], [data-component="related-item-headline"], [data-component="title"], article ${DEFAULT_SELECTOR}`,
|
||||||
},
|
},
|
||||||
{
|
"deno.land, docs.github.com": {
|
||||||
pattern: `deno.land, docs.github.com`,
|
|
||||||
selector: `main ${DEFAULT_SELECTOR}`,
|
selector: `main ${DEFAULT_SELECTOR}`,
|
||||||
|
keepSelector: DEFAULT_KEEP_SELECTOR,
|
||||||
},
|
},
|
||||||
{
|
"doc.rust-lang.org": {
|
||||||
pattern: `doc.rust-lang.org`,
|
selector: `.content ${DEFAULT_SELECTOR}`,
|
||||||
selector: `#content ${DEFAULT_SELECTOR}`,
|
keepSelector: DEFAULT_KEEP_SELECTOR,
|
||||||
},
|
},
|
||||||
{
|
"www.indiehackers.com": {
|
||||||
pattern: `www.indiehackers.com`,
|
|
||||||
selector: `h1, h3, .content ${DEFAULT_SELECTOR}, .feed-item__title-link`,
|
selector: `h1, h3, .content ${DEFAULT_SELECTOR}, .feed-item__title-link`,
|
||||||
},
|
},
|
||||||
{
|
"platform.openai.com/docs": {
|
||||||
pattern: `platform.openai.com/docs`,
|
|
||||||
selector: `.docs-body ${DEFAULT_SELECTOR}`,
|
selector: `.docs-body ${DEFAULT_SELECTOR}`,
|
||||||
|
keepSelector: DEFAULT_KEEP_SELECTOR,
|
||||||
},
|
},
|
||||||
{
|
"en.wikipedia.org": {
|
||||||
pattern: `en.wikipedia.org`,
|
|
||||||
selector: `h1, .mw-parser-output ${DEFAULT_SELECTOR}`,
|
selector: `h1, .mw-parser-output ${DEFAULT_SELECTOR}`,
|
||||||
|
keepSelector: `.mwe-math-element`,
|
||||||
},
|
},
|
||||||
{
|
"stackoverflow.com, serverfault.com, superuser.com, stackexchange.com, askubuntu.com, stackapps.com, mathoverflow.net":
|
||||||
pattern: `stackoverflow.com`,
|
{
|
||||||
selector: `h1, .s-prose p, .comment-body .comment-copy`,
|
selector: `.s-prose ${DEFAULT_SELECTOR}, .comment-copy, .question-hyperlink, .s-post-summary--content-title, .s-post-summary--content-excerpt`,
|
||||||
|
keepSelector: `${DEFAULT_KEEP_SELECTOR}, .math-container`,
|
||||||
|
},
|
||||||
|
"www.npmjs.com/package, developer.chrome.com/docs, medium.com, react.dev, create-react-app.dev, pytorch.org":
|
||||||
|
{
|
||||||
|
selector: `article ${DEFAULT_SELECTOR}`,
|
||||||
|
},
|
||||||
|
"news.ycombinator.com": {
|
||||||
|
selector: `.title, p`,
|
||||||
|
fixerSelector: `.toptext, .commtext`,
|
||||||
|
fixerFunc: FIXER_BR,
|
||||||
},
|
},
|
||||||
{
|
"github.com": {
|
||||||
pattern: `www.npmjs.com/package/, developer.chrome.com/docs, medium.com, developers.cloudflare.com, react.dev, create-react-app.dev, pytorch.org/`,
|
selector: `.markdown-body ${DEFAULT_SELECTOR}, .repo-description p, .Layout-sidebar .f4, .container-lg .py-4 .f5, .container-lg .my-4 .f5, .Box-row .pr-4, .Box-row article .mt-1, [itemprop="description"], .markdown-title, bdi, .ws-pre-wrap, .status-meta, span.status-meta, .col-10.color-fg-muted, .TimelineItem-body, .pinned-item-list-item-content .color-fg-muted, .markdown-body td, .markdown-body th`,
|
||||||
|
keepSelector: DEFAULT_KEEP_SELECTOR,
|
||||||
|
},
|
||||||
|
"twitter.com": {
|
||||||
|
selector: `[data-testid="tweetText"], [data-testid="birdwatch-pivot"]>div.css-1rynq56`,
|
||||||
|
keepSelector: `img, a, .r-18u37iz, .css-175oi2r`,
|
||||||
|
},
|
||||||
|
"m.youtube.com": {
|
||||||
|
selector: `.slim-video-information-title .yt-core-attributed-string, .media-item-headline .yt-core-attributed-string, .comment-text .yt-core-attributed-string, .typography-body-2b .yt-core-attributed-string, #ytp-caption-window-container .ytp-caption-segment`,
|
||||||
|
selectStyle: `-webkit-line-clamp: unset; max-height: none; height: auto;`,
|
||||||
|
parentStyle: `-webkit-line-clamp: unset; max-height: none; height: auto;`,
|
||||||
|
keepSelector: `img, #content-text>a`,
|
||||||
|
},
|
||||||
|
"www.youtube.com": {
|
||||||
|
selector: `h1, #video-title, #content-text, #title, yt-attributed-string>span>span, #ytp-caption-window-container .ytp-caption-segment`,
|
||||||
|
selectStyle: `-webkit-line-clamp: unset; max-height: none; height: auto;`,
|
||||||
|
parentStyle: `-webkit-line-clamp: unset; max-height: none; height: auto;`,
|
||||||
|
keepSelector: `img, #content-text>a`,
|
||||||
|
},
|
||||||
|
"bard.google.com": {
|
||||||
|
selector: `.query-content ${DEFAULT_SELECTOR}, message-content ${DEFAULT_SELECTOR}`,
|
||||||
|
},
|
||||||
|
"www.bing.com, copilot.microsoft.com": {
|
||||||
|
selector: `.b_algoSlug, .rwrl_padref; .cib-serp-main >>> .ac-textBlock ${DEFAULT_SELECTOR}, .text-message-content div`,
|
||||||
|
},
|
||||||
|
"www.phoronix.com": {
|
||||||
|
selector: `article ${DEFAULT_SELECTOR}`,
|
||||||
|
fixerSelector: `.content`,
|
||||||
|
fixerFunc: FIXER_BR,
|
||||||
|
},
|
||||||
|
"wx2.qq.com": {
|
||||||
|
selector: `.js_message_plain`,
|
||||||
|
},
|
||||||
|
"app.slack.com/client/": {
|
||||||
|
selector: `.p-rich_text_section, .c-message_attachment__text, .p-rich_text_list li`,
|
||||||
|
},
|
||||||
|
"discord.com/channels/": {
|
||||||
|
selector: `div[class^=message], div[class^=headerText], div[class^=name_], section[aria-label='Search Results'] div[id^=message-content], div[id^=message]`,
|
||||||
|
keepSelector: `li[class^='card'] div[class^='message'], [class^='embedFieldValue'], [data-list-item-id^='forum-channel-list'] div[class^='headerText']`,
|
||||||
|
},
|
||||||
|
"t.me/s/": {
|
||||||
|
selector: `.js-message_text ${DEFAULT_SELECTOR}`,
|
||||||
|
fixerSelector: `.tgme_widget_message_text`,
|
||||||
|
fixerFunc: FIXER_BR,
|
||||||
|
},
|
||||||
|
"web.telegram.org/k": {
|
||||||
|
selector: `div.kiss-p`,
|
||||||
|
keepSelector: `div[class^=time], .peer-title, .document-wrapper, .message.spoilers-container custom-emoji-element, reactions-element`,
|
||||||
|
fixerSelector: `.message`,
|
||||||
|
fixerFunc: FIXER_BN_DIV,
|
||||||
|
},
|
||||||
|
"web.telegram.org/a": {
|
||||||
|
selector: `.text-content > .kiss-p`,
|
||||||
|
keepSelector: `.Reactions, .time, .peer-title, .document-wrapper, .message.spoilers-container custom-emoji-element`,
|
||||||
|
fixerSelector: `.text-content`,
|
||||||
|
fixerFunc: FIXER_BR_DIV,
|
||||||
|
},
|
||||||
|
"www.instagram.com/": {
|
||||||
|
selector: `h1, article span[dir=auto] > span[dir=auto], ._ab1y`,
|
||||||
|
},
|
||||||
|
"www.instagram.com/p/,www.instagram.com/reels/": {
|
||||||
|
selector: `h1, div[class='x9f619 xjbqb8w x78zum5 x168nmei x13lgxp2 x5pf9jr xo71vjh x1uhb9sk x1plvlek xryxfnj x1c4vz4f x2lah0s xdt5ytf xqjyukv x1cy8zhl x1oa3qoh x1nhvcw1'] > span[class='x1lliihq x1plvlek xryxfnj x1n2onr6 x193iq5w xeuugli x1fj9vlw x13faqbe x1vvkbs x1s928wv xhkezso x1gmr53x x1cpjm7i x1fgarty x1943h6x x1i0vuye xvs91rp xo1l8bm x5n08af x10wh9bi x1wdrske x8viiok x18hxmgj'], span[class='x193iq5w xeuugli x1fj9vlw x13faqbe x1vvkbs xt0psk2 x1i0vuye xvs91rp xo1l8bm x5n08af x10wh9bi x1wdrske x8viiok x18hxmgj']`,
|
||||||
|
},
|
||||||
|
"mail.google.com": {
|
||||||
|
selector: `.a3s.aiL ${DEFAULT_SELECTOR}, span[data-thread-id]`,
|
||||||
|
fixerSelector: `.a3s.aiL`,
|
||||||
|
fixerFunc: FIXER_BR,
|
||||||
|
},
|
||||||
|
"web.whatsapp.com": {
|
||||||
|
selector: `.copyable-text > span`,
|
||||||
|
},
|
||||||
|
"chat.openai.com": {
|
||||||
|
selector: `div[data-message-author-role] > div ${DEFAULT_SELECTOR}`,
|
||||||
|
fixerSelector: `div[data-message-author-role='user'] > div`,
|
||||||
|
fixerFunc: FIXER_BN,
|
||||||
|
},
|
||||||
|
"forum.ru-board.com": {
|
||||||
|
selector: `.tit, .dats, .kiss-p, .lgf ${DEFAULT_SELECTOR}`,
|
||||||
|
fixerSelector: `span.post`,
|
||||||
|
fixerFunc: FIXER_BR,
|
||||||
|
},
|
||||||
|
"education.github.com": {
|
||||||
|
selector: `${DEFAULT_SELECTOR}, a, summary, span.Button-content`,
|
||||||
|
},
|
||||||
|
"blogs.windows.com": {
|
||||||
|
selector: `${DEFAULT_SELECTOR}, .c-uhf-nav-link, figcaption`,
|
||||||
|
fixerSelector: `.t-content>div>ul>li`,
|
||||||
|
fixerFunc: FIXER_BR,
|
||||||
|
},
|
||||||
|
"developer.apple.com/documentation/": {
|
||||||
|
selector: `#main ${DEFAULT_SELECTOR}, #main .abstract .content, #main .abstract.content, #main .link span`,
|
||||||
|
keepSelector: DEFAULT_KEEP_SELECTOR,
|
||||||
|
},
|
||||||
|
"greasyfork.org": {
|
||||||
|
selector: `h2, .script-link, .script-description, #additional-info ${DEFAULT_SELECTOR}`,
|
||||||
|
},
|
||||||
|
"www.fmkorea.com": {
|
||||||
|
selector: `#container ${DEFAULT_SELECTOR}`,
|
||||||
|
},
|
||||||
|
"forum.arduino.cc": {
|
||||||
|
selector: `.top-row>.title, .featured-topic>.title, .link-top-line>.title, .category-description, .topic-excerpt, .fancy-title, .cooked ${DEFAULT_SELECTOR}`,
|
||||||
|
},
|
||||||
|
"docs.arduino.cc": {
|
||||||
|
selector: `[class^="tutorial-module--left"] ${DEFAULT_SELECTOR}`,
|
||||||
|
},
|
||||||
|
"www.historydefined.net": {
|
||||||
|
selector: `.wp-element-caption, ${DEFAULT_SELECTOR}`,
|
||||||
|
},
|
||||||
|
"gobyexample.com": {
|
||||||
|
selector: `.docs p`,
|
||||||
|
keepSelector: `code`,
|
||||||
|
},
|
||||||
|
"go.dev/tour": {
|
||||||
|
selector: `#left-side ${DEFAULT_SELECTOR}`,
|
||||||
|
keepSelector: `code, img, svg >>> code`,
|
||||||
|
},
|
||||||
|
"pkg.go.dev": {
|
||||||
|
selector: `.Documentation-content ${DEFAULT_SELECTOR}`,
|
||||||
|
keepSelector: `${DEFAULT_KEEP_SELECTOR}, a, span`,
|
||||||
|
},
|
||||||
|
"docs.rs": {
|
||||||
|
selector: `.docblock ${DEFAULT_SELECTOR}, .docblock-short`,
|
||||||
|
keepSelector: `code >>> code`,
|
||||||
|
},
|
||||||
|
"randomnerdtutorials.com": {
|
||||||
selector: `article ${DEFAULT_SELECTOR}`,
|
selector: `article ${DEFAULT_SELECTOR}`,
|
||||||
},
|
},
|
||||||
{
|
"notebooks.githubusercontent.com/view/ipynb": {
|
||||||
pattern: `news.ycombinator.com`,
|
selector: `#notebook-container ${DEFAULT_SELECTOR}`,
|
||||||
selector: `.title, .commtext`,
|
keepSelector: DEFAULT_KEEP_SELECTOR,
|
||||||
},
|
},
|
||||||
{
|
"developers.cloudflare.com": {
|
||||||
pattern: `https://github.com/`,
|
selector: `article ${DEFAULT_SELECTOR}, .WorkerStarter--description`,
|
||||||
selector: `.markdown-body ${DEFAULT_SELECTOR}, .repo-description p, .Layout-sidebar .f4, .container-lg .py-4 .f5, .container-lg .my-4 .f5, .Box-row .pr-4, .Box-row article .mt-1, [itemprop='description'], .markdown-title, bdi`,
|
keepSelector: `a[rel='noopener'], code`,
|
||||||
},
|
},
|
||||||
{
|
"ubuntuforums.org": {
|
||||||
pattern: `twitter.com`,
|
fixerSelector: `.postcontent`,
|
||||||
selector: `[data-testid='tweetText']`,
|
fixerFunc: FIXER_BR,
|
||||||
},
|
},
|
||||||
{
|
"play.google.com/store/apps/details": {
|
||||||
pattern: `youtube.com`,
|
fixerSelector: `[data-g-id="description"]`,
|
||||||
selector: `h1, #video-title, #content-text, #title, yt-attributed-string>span>span`,
|
fixerFunc: FIXER_BR,
|
||||||
},
|
},
|
||||||
];
|
"news.yahoo.co.jp/articles/": {
|
||||||
|
fixerSelector: `.sc-cTsKDU`,
|
||||||
|
fixerFunc: FIXER_BN,
|
||||||
|
},
|
||||||
|
"chromereleases.googleblog.com": {
|
||||||
|
fixerSelector: `.post-content, .post-content > span, li > span`,
|
||||||
|
fixerFunc: FIXER_BR,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export const BUILTIN_RULES = RULES.sort((a, b) =>
|
export const BUILTIN_RULES = Object.entries(RULES_MAP)
|
||||||
a.pattern.localeCompare(b.pattern)
|
.sort((a, b) => a[0].localeCompare(b[0]))
|
||||||
).map((item) => ({
|
.map(([pattern, rule]) => ({
|
||||||
...DEFAULT_RULE,
|
...DEFAULT_RULE,
|
||||||
...item,
|
...rule,
|
||||||
transOpen: "true",
|
pattern,
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -1,81 +1,3 @@
|
|||||||
import { browser } from "./libs/browser";
|
import { run } from "./common";
|
||||||
import {
|
|
||||||
MSG_TRANS_TOGGLE,
|
|
||||||
MSG_TRANS_TOGGLE_STYLE,
|
|
||||||
MSG_TRANS_GETRULE,
|
|
||||||
MSG_TRANS_PUTRULE,
|
|
||||||
} from "./config";
|
|
||||||
import { getSettingWithDefault } from "./libs/storage";
|
|
||||||
import { isIframe, sendIframeMsg } from "./libs/iframe";
|
|
||||||
import { runWebfix } from "./libs/webfix";
|
|
||||||
import {
|
|
||||||
runIframe,
|
|
||||||
runTranslator,
|
|
||||||
showFab,
|
|
||||||
showTransbox,
|
|
||||||
windowListener,
|
|
||||||
showErr,
|
|
||||||
touchOperation,
|
|
||||||
} from "./common";
|
|
||||||
|
|
||||||
function runtimeListener(translator) {
|
run();
|
||||||
browser?.runtime.onMessage.addListener(async ({ action, args }) => {
|
|
||||||
switch (action) {
|
|
||||||
case MSG_TRANS_TOGGLE:
|
|
||||||
translator.toggle();
|
|
||||||
sendIframeMsg(MSG_TRANS_TOGGLE);
|
|
||||||
break;
|
|
||||||
case MSG_TRANS_TOGGLE_STYLE:
|
|
||||||
translator.toggleStyle();
|
|
||||||
sendIframeMsg(MSG_TRANS_TOGGLE_STYLE);
|
|
||||||
break;
|
|
||||||
case MSG_TRANS_GETRULE:
|
|
||||||
break;
|
|
||||||
case MSG_TRANS_PUTRULE:
|
|
||||||
translator.updateRule(args);
|
|
||||||
sendIframeMsg(MSG_TRANS_PUTRULE, args);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return { error: `message action is unavailable: ${action}` };
|
|
||||||
}
|
|
||||||
return { data: translator.rule };
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 入口函数
|
|
||||||
*/
|
|
||||||
(async () => {
|
|
||||||
try {
|
|
||||||
// 读取设置信息
|
|
||||||
const setting = await getSettingWithDefault();
|
|
||||||
|
|
||||||
// 适配iframe
|
|
||||||
if (isIframe) {
|
|
||||||
runIframe(setting);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 不规范网页修复
|
|
||||||
await runWebfix(setting);
|
|
||||||
|
|
||||||
// 翻译网页
|
|
||||||
const { translator, rule } = await runTranslator(setting);
|
|
||||||
|
|
||||||
// 监听消息
|
|
||||||
windowListener(rule);
|
|
||||||
runtimeListener(translator);
|
|
||||||
|
|
||||||
// 划词翻译
|
|
||||||
showTransbox(setting);
|
|
||||||
|
|
||||||
// 浮球按钮
|
|
||||||
await showFab(translator);
|
|
||||||
|
|
||||||
// 触屏操作
|
|
||||||
touchOperation(translator);
|
|
||||||
} catch (err) {
|
|
||||||
console.error("[KISS-Translator]", err);
|
|
||||||
showErr(err.message);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|||||||
@@ -8,7 +8,10 @@ export function useApi(translator) {
|
|||||||
|
|
||||||
const updateApi = useCallback(
|
const updateApi = useCallback(
|
||||||
async (obj) => {
|
async (obj) => {
|
||||||
const api = transApis[translator] || {};
|
const api = {
|
||||||
|
...DEFAULT_TRANS_APIS[translator],
|
||||||
|
...(transApis[translator] || {}),
|
||||||
|
};
|
||||||
Object.assign(transApis, { [translator]: { ...api, ...obj } });
|
Object.assign(transApis, { [translator]: { ...api, ...obj } });
|
||||||
await updateSetting({ transApis });
|
await updateSetting({ transApis });
|
||||||
},
|
},
|
||||||
@@ -20,5 +23,12 @@ export function useApi(translator) {
|
|||||||
await updateSetting({ transApis });
|
await updateSetting({ transApis });
|
||||||
}, [translator, transApis, updateSetting]);
|
}, [translator, transApis, updateSetting]);
|
||||||
|
|
||||||
return { api: transApis[translator] || {}, updateApi, resetApi };
|
return {
|
||||||
|
api: {
|
||||||
|
...DEFAULT_TRANS_APIS[translator],
|
||||||
|
...(transApis[translator] || {}),
|
||||||
|
},
|
||||||
|
updateApi,
|
||||||
|
resetApi,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
61
src/hooks/Audio.js
Normal file
61
src/hooks/Audio.js
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
|
import { apiBaiduTTS } from "../apis";
|
||||||
|
import { kissLog } from "../libs/log";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 声音播放hook
|
||||||
|
* @param {*} src
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function useAudio(src) {
|
||||||
|
const audioRef = useRef(null);
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
const [ready, setReady] = useState(false);
|
||||||
|
const [playing, setPlaying] = useState(false);
|
||||||
|
|
||||||
|
const onPlay = useCallback(() => {
|
||||||
|
audioRef.current?.play();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!src) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const audio = new Audio(src);
|
||||||
|
audio.addEventListener("error", (err) => setError(err));
|
||||||
|
audio.addEventListener("canplaythrough", () => setReady(true));
|
||||||
|
audio.addEventListener("play", () => setPlaying(true));
|
||||||
|
audio.addEventListener("ended", () => setPlaying(false));
|
||||||
|
audioRef.current = audio;
|
||||||
|
}, [src]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
error,
|
||||||
|
ready,
|
||||||
|
playing,
|
||||||
|
onPlay,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取语音hook
|
||||||
|
* @param {*} text
|
||||||
|
* @param {*} lan
|
||||||
|
* @param {*} spd
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function useTextAudio(text, lan = "uk", spd = 3) {
|
||||||
|
const [src, setSrc] = useState("");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
setSrc(await apiBaiduTTS(text, lan, spd));
|
||||||
|
} catch (err) {
|
||||||
|
kissLog(err, "baidu tts");
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}, [text, lan, spd]);
|
||||||
|
|
||||||
|
return useAudio(src);
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import { useCallback, useEffect, useState } from "react";
|
|||||||
import { trySyncWords } from "../libs/sync";
|
import { trySyncWords } from "../libs/sync";
|
||||||
import { getWordsWithDefault, setWords } from "../libs/storage";
|
import { getWordsWithDefault, setWords } from "../libs/storage";
|
||||||
import { useSyncMeta } from "./Sync";
|
import { useSyncMeta } from "./Sync";
|
||||||
|
import { kissLog } from "../libs/log";
|
||||||
|
|
||||||
export function useFavWords() {
|
export function useFavWords() {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
@@ -56,7 +57,7 @@ export function useFavWords() {
|
|||||||
const favWords = await getWordsWithDefault();
|
const favWords = await getWordsWithDefault();
|
||||||
setFavWords(favWords);
|
setFavWords(favWords);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("[query fav]", err);
|
kissLog(err, "query fav");
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { storage } from "../libs/storage";
|
import { storage } from "../libs/storage";
|
||||||
|
import { kissLog } from "../libs/log";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {*} key
|
* @param {*} key
|
||||||
* @param {*} defaultVal 需为调用hook外的常量
|
* @param {*} defaultVal 需为调用hook外的常量
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function useStorage(key, defaultVal) {
|
export function useStorage(key, defaultVal) {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
@@ -40,7 +41,7 @@ export function useStorage(key, defaultVal) {
|
|||||||
setData(val);
|
setData(val);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("[storage reload]", err.message);
|
kissLog(err, "storage reload");
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -58,7 +59,7 @@ export function useStorage(key, defaultVal) {
|
|||||||
await storage.setObj(key, defaultVal);
|
await storage.setObj(key, defaultVal);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("[storage load]", err.message);
|
kissLog(err, "storage load");
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useSetting } from "./Setting";
|
|||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { loadOrFetchSubRules } from "../libs/subRules";
|
import { loadOrFetchSubRules } from "../libs/subRules";
|
||||||
import { delSubRules } from "../libs/storage";
|
import { delSubRules } from "../libs/storage";
|
||||||
|
import { kissLog } from "../libs/log";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 订阅规则
|
* 订阅规则
|
||||||
@@ -72,7 +73,7 @@ export function useSubRules() {
|
|||||||
const rules = await loadOrFetchSubRules(selectedUrl);
|
const rules = await loadOrFetchSubRules(selectedUrl);
|
||||||
setSelectedRules(rules);
|
setSelectedRules(rules);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("[loadOrFetchSubRules]", err);
|
kissLog(err, "loadOrFetchSubRules");
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { ThemeProvider, createTheme } from "@mui/material/styles";
|
import { ThemeProvider, createTheme } from "@mui/material/styles";
|
||||||
import CssBaseline from "@mui/material/CssBaseline";
|
import { CssBaseline, GlobalStyles } from "@mui/material";
|
||||||
import { useDarkMode } from "./ColorMode";
|
import { useDarkMode } from "./ColorMode";
|
||||||
import { THEME_DARK, THEME_LIGHT } from "../config";
|
import { THEME_DARK, THEME_LIGHT } from "../config";
|
||||||
|
|
||||||
@@ -9,13 +9,27 @@ import { THEME_DARK, THEME_LIGHT } from "../config";
|
|||||||
* @param {*} param0
|
* @param {*} param0
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export default function Theme({ children, options }) {
|
export default function Theme({ children, options, styles }) {
|
||||||
const { darkMode } = useDarkMode();
|
const { darkMode } = useDarkMode();
|
||||||
const theme = useMemo(() => {
|
const theme = useMemo(() => {
|
||||||
|
let htmlFontSize = 16;
|
||||||
|
try {
|
||||||
|
const s = window.getComputedStyle(document.body.parentNode).fontSize;
|
||||||
|
const fontSize = parseInt(s.replace("px", ""));
|
||||||
|
if (fontSize > 0 && fontSize < 1000) {
|
||||||
|
htmlFontSize = fontSize;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
return createTheme({
|
return createTheme({
|
||||||
palette: {
|
palette: {
|
||||||
mode: darkMode ? THEME_DARK : THEME_LIGHT,
|
mode: darkMode ? THEME_DARK : THEME_LIGHT,
|
||||||
},
|
},
|
||||||
|
typography: {
|
||||||
|
htmlFontSize,
|
||||||
|
},
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
}, [darkMode, options]);
|
}, [darkMode, options]);
|
||||||
@@ -24,6 +38,7 @@ export default function Theme({ children, options }) {
|
|||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
|
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
|
<GlobalStyles styles={styles} />
|
||||||
{children}
|
{children}
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ 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";
|
import { DEFAULT_TRANS_APIS } from "../config";
|
||||||
|
import { kissLog } from "../libs/log";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 翻译hook
|
* 翻译hook
|
||||||
@@ -16,15 +17,21 @@ export function useTranslate(q, rule, setting) {
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [sameLang, setSamelang] = useState(false);
|
const [sameLang, setSamelang] = useState(false);
|
||||||
|
|
||||||
const { translator, fromLang, toLang } = rule;
|
const { translator, fromLang, toLang, detectRemote, skipLangs = [] } = rule;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
const deLang = await tryDetectLang(q, setting.detectRemote);
|
if (!q.replace(/\[(\d+)\]/g, "").trim()) {
|
||||||
if (deLang && toLang.includes(deLang)) {
|
setText(q);
|
||||||
|
setSamelang(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const deLang = await tryDetectLang(q, detectRemote === "true");
|
||||||
|
if (deLang && (toLang.includes(deLang) || skipLangs.includes(deLang))) {
|
||||||
setSamelang(true);
|
setSamelang(true);
|
||||||
} else {
|
} else {
|
||||||
const [trText, isSame] = await apiTranslate({
|
const [trText, isSame] = await apiTranslate({
|
||||||
@@ -32,19 +39,21 @@ export function useTranslate(q, rule, setting) {
|
|||||||
text: q,
|
text: q,
|
||||||
fromLang,
|
fromLang,
|
||||||
toLang,
|
toLang,
|
||||||
apiSetting:
|
apiSetting: {
|
||||||
setting.transApis?.[translator] || DEFAULT_TRANS_APIS[translator],
|
...DEFAULT_TRANS_APIS[translator],
|
||||||
|
...(setting.transApis[translator] || {}),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
setText(trText);
|
setText(trText);
|
||||||
setSamelang(isSame);
|
setSamelang(isSame);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("[translate]", err);
|
kissLog(err, "translate");
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}, [q, translator, fromLang, toLang, setting]);
|
}, [q, translator, fromLang, toLang, detectRemote, skipLangs, setting]);
|
||||||
|
|
||||||
return { text, sameLang, loading };
|
return { text, sameLang, loading };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,58 +0,0 @@
|
|||||||
import { STOKEY_WFRULES, KV_WFRULES_KEY } from "../config";
|
|
||||||
import { useStorage } from "./Storage";
|
|
||||||
import { trySyncWebfixRules } from "../libs/sync";
|
|
||||||
import { useCallback } from "react";
|
|
||||||
import { useSyncMeta } from "./Sync";
|
|
||||||
|
|
||||||
const DEFAULT_WFRULES = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 修复规则 hook
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export function useWebfixRules() {
|
|
||||||
const { data: list, save } = useStorage(STOKEY_WFRULES, DEFAULT_WFRULES);
|
|
||||||
const { updateSyncMeta } = useSyncMeta();
|
|
||||||
|
|
||||||
const updateRules = useCallback(
|
|
||||||
async (rules) => {
|
|
||||||
await save(rules);
|
|
||||||
await updateSyncMeta(KV_WFRULES_KEY);
|
|
||||||
trySyncWebfixRules();
|
|
||||||
},
|
|
||||||
[save, updateSyncMeta]
|
|
||||||
);
|
|
||||||
|
|
||||||
const add = useCallback(
|
|
||||||
async (rule) => {
|
|
||||||
const rules = [...list];
|
|
||||||
if (rules.map((item) => item.pattern).includes(rule.pattern)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
rules.unshift(rule);
|
|
||||||
await updateRules(rules);
|
|
||||||
},
|
|
||||||
[list, updateRules]
|
|
||||||
);
|
|
||||||
|
|
||||||
const del = useCallback(
|
|
||||||
async (pattern) => {
|
|
||||||
let rules = [...list];
|
|
||||||
rules = rules.filter((item) => item.pattern !== pattern);
|
|
||||||
await updateRules(rules);
|
|
||||||
},
|
|
||||||
[list, updateRules]
|
|
||||||
);
|
|
||||||
|
|
||||||
const put = useCallback(
|
|
||||||
async (pattern, obj) => {
|
|
||||||
const rules = [...list];
|
|
||||||
const rule = rules.find((r) => r.pattern === pattern);
|
|
||||||
rule && Object.assign(rule, obj);
|
|
||||||
await updateRules(rules);
|
|
||||||
},
|
|
||||||
[list, updateRules]
|
|
||||||
);
|
|
||||||
|
|
||||||
return { list, add, del, put };
|
|
||||||
}
|
|
||||||
@@ -36,19 +36,10 @@ function App() {
|
|||||||
<Link href={process.env.REACT_APP_USERSCRIPT_DOWNLOADURL}>
|
<Link href={process.env.REACT_APP_USERSCRIPT_DOWNLOADURL}>
|
||||||
Install/Update Userscript for Tampermonkey/Violentmonkey
|
Install/Update Userscript for Tampermonkey/Violentmonkey
|
||||||
</Link>
|
</Link>
|
||||||
{/* <Link href={process.env.REACT_APP_USERSCRIPT_DOWNLOADURL2}>
|
|
||||||
Install/Update Userscript for Tampermonkey/Violentmonkey 2
|
|
||||||
</Link> */}
|
|
||||||
<Link href={process.env.REACT_APP_USERSCRIPT_IOS_DOWNLOADURL}>
|
<Link href={process.env.REACT_APP_USERSCRIPT_IOS_DOWNLOADURL}>
|
||||||
Install/Update Userscript for iOS Safari
|
Install/Update Userscript for iOS Safari
|
||||||
</Link>
|
</Link>
|
||||||
{/* <Link href={process.env.REACT_APP_USERSCRIPT_IOS_DOWNLOADURL2}>
|
|
||||||
Install/Update Userscript for iOS Safari 2
|
|
||||||
</Link> */}
|
|
||||||
<Link href={process.env.REACT_APP_OPTIONSPAGE}>Open Options Page</Link>
|
<Link href={process.env.REACT_APP_OPTIONSPAGE}>Open Options Page</Link>
|
||||||
{/* <Link href={process.env.REACT_APP_OPTIONSPAGE2}>
|
|
||||||
Open Options Page 2
|
|
||||||
</Link> */}
|
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
{loading ? (
|
{loading ? (
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { getMsauth, setMsauth } from "./storage";
|
import { getMsauth, setMsauth } from "./storage";
|
||||||
import { URL_MICROSOFT_AUTH } from "../config";
|
import { URL_MICROSOFT_AUTH } from "../config";
|
||||||
import { fetchData } from "./fetch";
|
import { fetchData } from "./fetch";
|
||||||
|
import { kissLog } from "./log";
|
||||||
|
|
||||||
const parseMSToken = (token) => {
|
const parseMSToken = (token) => {
|
||||||
try {
|
try {
|
||||||
return JSON.parse(atob(token.split(".")[1])).exp;
|
return JSON.parse(atob(token.split(".")[1])).exp;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("[parseMSToken]", err);
|
kissLog(err, "parseMSToken");
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|||||||
10
src/libs/blacklist.js
Normal file
10
src/libs/blacklist.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { isMatch } from "./utils";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否在黑名单中
|
||||||
|
* @param {*} href
|
||||||
|
* @param {*} param1
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const isInBlacklist = (href, { blacklist }) =>
|
||||||
|
blacklist.split(/\n|,/).some((url) => isMatch(href, url.trim()));
|
||||||
@@ -8,7 +8,7 @@ function _browser() {
|
|||||||
try {
|
try {
|
||||||
return require("webextension-polyfill");
|
return require("webextension-polyfill");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// console.log("[browser]", err.message);
|
// kissLog(err, "browser");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ import {
|
|||||||
} from "../config";
|
} from "../config";
|
||||||
import { isBg } from "./browser";
|
import { isBg } from "./browser";
|
||||||
import { newCacheReq, newTransReq } from "./req";
|
import { newCacheReq, newTransReq } from "./req";
|
||||||
|
import { kissLog } from "./log";
|
||||||
|
import { blobToBase64 } from "./utils";
|
||||||
|
|
||||||
|
const TIMEOUT = 5000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 油猴脚本的请求封装
|
* 油猴脚本的请求封装
|
||||||
@@ -26,6 +30,7 @@ export const fetchGM = async (input, { method = "GET", headers, body } = {}) =>
|
|||||||
headers,
|
headers,
|
||||||
data: body,
|
data: body,
|
||||||
// withCredentials: true,
|
// withCredentials: true,
|
||||||
|
timeout: TIMEOUT,
|
||||||
onload: ({ response, responseHeaders, status, statusText, ...opts }) => {
|
onload: ({ response, responseHeaders, status, statusText, ...opts }) => {
|
||||||
const headers = {};
|
const headers = {};
|
||||||
responseHeaders.split("\n").forEach((line) => {
|
responseHeaders.split("\n").forEach((line) => {
|
||||||
@@ -86,6 +91,10 @@ export const fetchApi = async ({ input, init, transOpts, apiSetting }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (AbortSignal?.timeout) {
|
||||||
|
Object.assign(init, { signal: AbortSignal.timeout(TIMEOUT) });
|
||||||
|
}
|
||||||
|
|
||||||
return fetch(input, init);
|
return fetch(input, init);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -118,7 +127,7 @@ export const fetchData = async (
|
|||||||
const cache = await caches.open(CACHE_NAME);
|
const cache = await caches.open(CACHE_NAME);
|
||||||
res = await cache.match(cacheReq);
|
res = await cache.match(cacheReq);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("[cache match]", err.message);
|
kissLog(err, "cache match");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,14 +139,17 @@ export const fetchData = async (
|
|||||||
res = await fetchApi({ input, init, transOpts, apiSetting });
|
res = await fetchApi({ input, init, transOpts, apiSetting });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!res?.ok) {
|
if (!res) {
|
||||||
const cause = {
|
throw new Error("Unknow error");
|
||||||
|
} else if (!res.ok) {
|
||||||
|
const msg = {
|
||||||
|
url: res.url,
|
||||||
status: res.status,
|
status: res.status,
|
||||||
};
|
};
|
||||||
if (res.headers.get("Content-Type")?.includes("json")) {
|
if (res.headers.get("Content-Type")?.includes("json")) {
|
||||||
cause.body = await res.json();
|
msg.response = await res.json();
|
||||||
}
|
}
|
||||||
throw new Error(`response: [${res.status}] ${res.statusText}`, { cause });
|
throw new Error(JSON.stringify(msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 插入缓存
|
// 插入缓存
|
||||||
@@ -146,7 +158,7 @@ export const fetchData = async (
|
|||||||
const cache = await caches.open(CACHE_NAME);
|
const cache = await caches.open(CACHE_NAME);
|
||||||
await cache.put(cacheReq, res.clone());
|
await cache.put(cacheReq, res.clone());
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("[cache put]", err.message);
|
kissLog(err, "cache put");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -154,6 +166,9 @@ export const fetchData = async (
|
|||||||
const contentType = res.headers.get("Content-Type");
|
const contentType = res.headers.get("Content-Type");
|
||||||
if (contentType?.includes("json")) {
|
if (contentType?.includes("json")) {
|
||||||
return await res.json();
|
return await res.json();
|
||||||
|
} else if (contentType?.includes("audio")) {
|
||||||
|
const blob = await res.blob();
|
||||||
|
return await blobToBase64(blob);
|
||||||
}
|
}
|
||||||
return await res.text();
|
return await res.text();
|
||||||
};
|
};
|
||||||
@@ -171,11 +186,7 @@ export const fetchPolyfill = async (input, opts) => {
|
|||||||
|
|
||||||
// 插件
|
// 插件
|
||||||
if (isExt && !isBg()) {
|
if (isExt && !isBg()) {
|
||||||
const res = await sendBgMsg(MSG_FETCH, { input, opts });
|
return await sendBgMsg(MSG_FETCH, { input, opts });
|
||||||
if (res.error) {
|
|
||||||
throw new Error(res.error, { cause: res.cause });
|
|
||||||
}
|
|
||||||
return res.data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 油猴/网页/BackgroundPage
|
// 油猴/网页/BackgroundPage
|
||||||
@@ -189,10 +200,7 @@ export const fetchPolyfill = async (input, opts) => {
|
|||||||
*/
|
*/
|
||||||
export const updateFetchPool = async (interval, limit) => {
|
export const updateFetchPool = async (interval, limit) => {
|
||||||
if (isExt) {
|
if (isExt) {
|
||||||
const res = await sendBgMsg(MSG_FETCH_LIMIT, { interval, limit });
|
await sendBgMsg(MSG_FETCH_LIMIT, { interval, limit });
|
||||||
if (res.error) {
|
|
||||||
throw new Error(res.error);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
fetchPool.update(interval, limit);
|
fetchPool.update(interval, limit);
|
||||||
}
|
}
|
||||||
@@ -203,10 +211,7 @@ export const updateFetchPool = async (interval, limit) => {
|
|||||||
*/
|
*/
|
||||||
export const clearFetchPool = async () => {
|
export const clearFetchPool = async () => {
|
||||||
if (isExt) {
|
if (isExt) {
|
||||||
const res = await sendBgMsg(MSG_FETCH_CLEAR);
|
await sendBgMsg(MSG_FETCH_CLEAR);
|
||||||
if (res.error) {
|
|
||||||
throw new Error(res.error);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
fetchPool.clear();
|
fetchPool.clear();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { CACHE_NAME } from "../config";
|
import { CACHE_NAME } from "../config";
|
||||||
import { browser } from "./browser";
|
import { browser } from "./browser";
|
||||||
import { apiBaiduLangdetect } from "../apis";
|
import { apiBaiduLangdetect } from "../apis";
|
||||||
|
import { kissLog } from "./log";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 清除缓存数据
|
* 清除缓存数据
|
||||||
@@ -9,7 +10,7 @@ export const tryClearCaches = async () => {
|
|||||||
try {
|
try {
|
||||||
caches.delete(CACHE_NAME);
|
caches.delete(CACHE_NAME);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("[clean caches]", err.message);
|
kissLog(err, "clean caches");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -25,7 +26,7 @@ export const tryDetectLang = async (q, useRemote = false) => {
|
|||||||
try {
|
try {
|
||||||
lang = await apiBaiduLangdetect(q);
|
lang = await apiBaiduLangdetect(q);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("[detect lang remote]", err.message);
|
kissLog(err, "detect lang remote");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,7 +35,7 @@ export const tryDetectLang = async (q, useRemote = false) => {
|
|||||||
const res = await browser?.i18n?.detectLanguage(q);
|
const res = await browser?.i18n?.detectLanguage(q);
|
||||||
lang = res?.languages?.[0]?.language;
|
lang = res?.languages?.[0]?.language;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("[detect lang local]", err.message);
|
kissLog(err, "detect lang local");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
35
src/libs/injector.js
Normal file
35
src/libs/injector.js
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
// Function to inject inline JavaScript code
|
||||||
|
export const injectInlineJs = (code) => {
|
||||||
|
const el = document.createElement("script");
|
||||||
|
el.setAttribute("data-source", "KISS-Calendar injectInlineJs");
|
||||||
|
el.setAttribute("type", "text/javascript");
|
||||||
|
el.textContent = code;
|
||||||
|
document.body?.appendChild(el);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function to inject external JavaScript file
|
||||||
|
export const injectExternalJs = (src) => {
|
||||||
|
const el = document.createElement("script");
|
||||||
|
el.setAttribute("data-source", "KISS-Calendar injectExternalJs");
|
||||||
|
el.setAttribute("type", "text/javascript");
|
||||||
|
el.setAttribute("src", src);
|
||||||
|
document.body?.appendChild(el);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function to inject internal CSS code
|
||||||
|
export const injectInternalCss = (styles) => {
|
||||||
|
const el = document.createElement("style");
|
||||||
|
el.setAttribute("data-source", "KISS-Calendar injectInternalCss");
|
||||||
|
el.textContent = styles;
|
||||||
|
document.head?.appendChild(el);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function to inject external CSS file
|
||||||
|
export const injectExternalCss = (href) => {
|
||||||
|
const el = document.createElement("link");
|
||||||
|
el.setAttribute("data-source", "KISS-Calendar injectExternalCss");
|
||||||
|
el.setAttribute("rel", "stylesheet");
|
||||||
|
el.setAttribute("type", "text/css");
|
||||||
|
el.setAttribute("href", href);
|
||||||
|
document.head?.appendChild(el);
|
||||||
|
};
|
||||||
199
src/libs/inputTranslate.js
Normal file
199
src/libs/inputTranslate.js
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
import {
|
||||||
|
DEFAULT_INPUT_RULE,
|
||||||
|
DEFAULT_TRANS_APIS,
|
||||||
|
DEFAULT_INPUT_SHORTCUT,
|
||||||
|
OPT_LANGS_LIST,
|
||||||
|
} from "../config";
|
||||||
|
import { genEventName, removeEndchar, matchInputStr, sleep } from "./utils";
|
||||||
|
import { stepShortcutRegister } from "./shortcut";
|
||||||
|
import { apiTranslate } from "../apis";
|
||||||
|
import { loadingSvg } from "./svg";
|
||||||
|
import { kissLog } from "./log";
|
||||||
|
|
||||||
|
function isInputNode(node) {
|
||||||
|
return node.nodeName === "INPUT" || node.nodeName === "TEXTAREA";
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEditAbleNode(node) {
|
||||||
|
return node.hasAttribute("contenteditable");
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectContent(node) {
|
||||||
|
node.focus();
|
||||||
|
const range = document.createRange();
|
||||||
|
range.selectNodeContents(node);
|
||||||
|
|
||||||
|
const selection = window.getSelection();
|
||||||
|
selection.removeAllRanges();
|
||||||
|
selection.addRange(range);
|
||||||
|
}
|
||||||
|
|
||||||
|
function pasteContentEvent(node, text) {
|
||||||
|
node.focus();
|
||||||
|
const data = new DataTransfer();
|
||||||
|
data.setData("text/plain", text);
|
||||||
|
|
||||||
|
const event = new ClipboardEvent("paste", { clipboardData: data });
|
||||||
|
document.dispatchEvent(event);
|
||||||
|
data.clearData();
|
||||||
|
}
|
||||||
|
|
||||||
|
function pasteContentCommand(node, text) {
|
||||||
|
node.focus();
|
||||||
|
document.execCommand("insertText", false, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
function collapseToEnd(node) {
|
||||||
|
node.focus();
|
||||||
|
const selection = window.getSelection();
|
||||||
|
selection.collapseToEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNodeText(node) {
|
||||||
|
if (isInputNode(node)) {
|
||||||
|
return node.value;
|
||||||
|
}
|
||||||
|
return node.innerText || node.textContent || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function addLoading(node, loadingId) {
|
||||||
|
const div = document.createElement("div");
|
||||||
|
div.id = loadingId;
|
||||||
|
div.innerHTML = loadingSvg;
|
||||||
|
div.style.cssText = `
|
||||||
|
width: ${node.offsetWidth}px;
|
||||||
|
height: ${node.offsetHeight}px;
|
||||||
|
line-height: ${node.offsetHeight}px;
|
||||||
|
position: absolute;
|
||||||
|
text-align: center;
|
||||||
|
left: ${node.offsetLeft}px;
|
||||||
|
top: ${node.offsetTop}px;
|
||||||
|
z-index: 2147483647;
|
||||||
|
`;
|
||||||
|
node.offsetParent?.appendChild(div);
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeLoading(node, loadingId) {
|
||||||
|
const div = node.offsetParent.querySelector(`#${loadingId}`);
|
||||||
|
if (div) {
|
||||||
|
div.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 输入框翻译
|
||||||
|
*/
|
||||||
|
export default function inputTranslate({
|
||||||
|
inputRule: {
|
||||||
|
transOpen,
|
||||||
|
triggerShortcut,
|
||||||
|
translator,
|
||||||
|
fromLang,
|
||||||
|
toLang,
|
||||||
|
triggerCount,
|
||||||
|
triggerTime,
|
||||||
|
transSign,
|
||||||
|
} = DEFAULT_INPUT_RULE,
|
||||||
|
transApis,
|
||||||
|
}) {
|
||||||
|
if (!transOpen) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiSetting = transApis?.[translator] || DEFAULT_TRANS_APIS[translator];
|
||||||
|
if (triggerShortcut.length === 0) {
|
||||||
|
triggerShortcut = DEFAULT_INPUT_SHORTCUT;
|
||||||
|
triggerCount = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
stepShortcutRegister(
|
||||||
|
triggerShortcut,
|
||||||
|
async () => {
|
||||||
|
let node = document.activeElement;
|
||||||
|
|
||||||
|
if (!node) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (node.shadowRoot) {
|
||||||
|
node = node.shadowRoot.activeElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isInputNode(node) && !isEditAbleNode(node)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let initText = getNodeText(node);
|
||||||
|
if (triggerShortcut.length === 1 && triggerShortcut[0].length === 1) {
|
||||||
|
// todo: remove multiple char
|
||||||
|
initText = removeEndchar(initText, triggerShortcut[0], triggerCount);
|
||||||
|
}
|
||||||
|
if (!initText.trim()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let text = initText;
|
||||||
|
if (transSign) {
|
||||||
|
const res = matchInputStr(text, transSign);
|
||||||
|
if (res) {
|
||||||
|
let lang = res[1];
|
||||||
|
if (lang === "zh" || lang === "cn") {
|
||||||
|
lang = "zh-CN";
|
||||||
|
} else if (lang === "tw" || lang === "hk") {
|
||||||
|
lang = "zh-TW";
|
||||||
|
}
|
||||||
|
if (lang && OPT_LANGS_LIST.includes(lang)) {
|
||||||
|
toLang = lang;
|
||||||
|
}
|
||||||
|
text = res[2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.log("input -->", text);
|
||||||
|
|
||||||
|
const loadingId = "kiss-" + genEventName();
|
||||||
|
try {
|
||||||
|
addLoading(node, loadingId);
|
||||||
|
|
||||||
|
const [trText, isSame] = await apiTranslate({
|
||||||
|
translator,
|
||||||
|
text,
|
||||||
|
fromLang,
|
||||||
|
toLang,
|
||||||
|
apiSetting,
|
||||||
|
});
|
||||||
|
if (!trText || isSame) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isInputNode(node)) {
|
||||||
|
node.value = trText;
|
||||||
|
node.dispatchEvent(
|
||||||
|
new Event("input", { bubbles: true, cancelable: true })
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectContent(node);
|
||||||
|
await sleep(200);
|
||||||
|
|
||||||
|
pasteContentEvent(node, trText);
|
||||||
|
await sleep(200);
|
||||||
|
|
||||||
|
// todo: use includes?
|
||||||
|
if (getNodeText(node).startsWith(initText)) {
|
||||||
|
pasteContentCommand(node, trText);
|
||||||
|
await sleep(100);
|
||||||
|
} else {
|
||||||
|
collapseToEnd(node);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
kissLog(err, "translate input");
|
||||||
|
} finally {
|
||||||
|
removeLoading(node, loadingId);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
triggerCount,
|
||||||
|
triggerTime
|
||||||
|
);
|
||||||
|
}
|
||||||
12
src/libs/log.js
Normal file
12
src/libs/log.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* 日志函数
|
||||||
|
* @param {*} msg
|
||||||
|
* @param {*} type
|
||||||
|
*/
|
||||||
|
export const kissLog = (msg, type) => {
|
||||||
|
let prefix = `[KISS-Translator]`;
|
||||||
|
if (type) {
|
||||||
|
prefix += `[${type}]`;
|
||||||
|
}
|
||||||
|
console.log(`${prefix} ${msg}`);
|
||||||
|
};
|
||||||
@@ -1,5 +1,22 @@
|
|||||||
import { browser } from "./browser";
|
import { browser } from "./browser";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前tab信息
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getCurTab = async () => {
|
||||||
|
const [tab] = await browser.tabs.query({
|
||||||
|
active: true,
|
||||||
|
lastFocusedWindow: true,
|
||||||
|
});
|
||||||
|
return tab;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getCurTabId = async () => {
|
||||||
|
const tab = await getCurTab();
|
||||||
|
return tab.id;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 发送消息给background
|
* 发送消息给background
|
||||||
* @param {*} action
|
* @param {*} action
|
||||||
@@ -16,15 +33,6 @@ export const sendBgMsg = (action, args) =>
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const sendTabMsg = async (action, args) => {
|
export const sendTabMsg = async (action, args) => {
|
||||||
const tabs = await browser.tabs.query({ active: true, currentWindow: true });
|
const tabId = await getCurTabId();
|
||||||
return browser.tabs.sendMessage(tabs[0].id, { action, args });
|
return browser.tabs.sendMessage(tabId, { action, args });
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前tab信息
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export const getTabInfo = async () => {
|
|
||||||
const tabs = await browser.tabs.query({ active: true, currentWindow: true });
|
|
||||||
return tabs[0];
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { kissLog } from "./log";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 任务池
|
* 任务池
|
||||||
* @param {*} fn
|
* @param {*} fn
|
||||||
@@ -35,7 +37,7 @@ export const taskPool = (
|
|||||||
const res = await fn({ ...args, ...preArgs });
|
const res = await fn({ ...args, ...preArgs });
|
||||||
resolve(res);
|
resolve(res);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("[task]", retry, err);
|
kissLog(err, "task");
|
||||||
if (retry < maxRetry) {
|
if (retry < maxRetry) {
|
||||||
const retryTimer = setTimeout(() => {
|
const retryTimer = setTimeout(() => {
|
||||||
clearTimeout(retryTimer);
|
clearTimeout(retryTimer);
|
||||||
|
|||||||
135
src/libs/req.js
135
src/libs/req.js
@@ -5,20 +5,49 @@ import {
|
|||||||
OPT_TRANS_DEEPL,
|
OPT_TRANS_DEEPL,
|
||||||
OPT_TRANS_DEEPLFREE,
|
OPT_TRANS_DEEPLFREE,
|
||||||
OPT_TRANS_DEEPLX,
|
OPT_TRANS_DEEPLX,
|
||||||
|
OPT_TRANS_NIUTRANS,
|
||||||
OPT_TRANS_BAIDU,
|
OPT_TRANS_BAIDU,
|
||||||
OPT_TRANS_TENCENT,
|
OPT_TRANS_TENCENT,
|
||||||
OPT_TRANS_OPENAI,
|
OPT_TRANS_OPENAI,
|
||||||
|
OPT_TRANS_GEMINI,
|
||||||
OPT_TRANS_CLOUDFLAREAI,
|
OPT_TRANS_CLOUDFLAREAI,
|
||||||
OPT_TRANS_CUSTOMIZE,
|
OPT_TRANS_CUSTOMIZE,
|
||||||
|
OPT_TRANS_CUSTOMIZE_2,
|
||||||
|
OPT_TRANS_CUSTOMIZE_3,
|
||||||
|
OPT_TRANS_CUSTOMIZE_4,
|
||||||
|
OPT_TRANS_CUSTOMIZE_5,
|
||||||
URL_MICROSOFT_TRAN,
|
URL_MICROSOFT_TRAN,
|
||||||
URL_TENCENT_TRANSMART,
|
URL_TENCENT_TRANSMART,
|
||||||
PROMPT_PLACE_FROM,
|
INPUT_PLACE_URL,
|
||||||
PROMPT_PLACE_TO,
|
INPUT_PLACE_FROM,
|
||||||
|
INPUT_PLACE_TO,
|
||||||
|
INPUT_PLACE_TEXT,
|
||||||
|
INPUT_PLACE_KEY,
|
||||||
} from "../config";
|
} from "../config";
|
||||||
import { msAuth } from "./auth";
|
import { msAuth } from "./auth";
|
||||||
import { genDeeplFree } from "../apis/deepl";
|
import { genDeeplFree } from "../apis/deepl";
|
||||||
import { genBaidu } from "../apis/baidu";
|
import { genBaidu } from "../apis/baidu";
|
||||||
|
|
||||||
|
const keyMap = new Map();
|
||||||
|
|
||||||
|
// 轮询key
|
||||||
|
const keyPick = (translator, key = "") => {
|
||||||
|
const keys = key
|
||||||
|
.split(/\n|,/)
|
||||||
|
.map((item) => item.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
if (keys.length === 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const preIndex = keyMap.get(translator) ?? -1;
|
||||||
|
const curIndex = (preIndex + 1) % keys.length;
|
||||||
|
keyMap.set(translator, curIndex);
|
||||||
|
|
||||||
|
return keys[curIndex];
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构造缓存 request
|
* 构造缓存 request
|
||||||
* @param {*} request
|
* @param {*} request
|
||||||
@@ -119,6 +148,27 @@ const genDeeplX = ({ text, from, to, url, key }) => {
|
|||||||
return [url, init];
|
return [url, init];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const genNiuTrans = ({ text, from, to, url, key, dictNo, memoryNo }) => {
|
||||||
|
const data = {
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
apikey: key,
|
||||||
|
src_text: text,
|
||||||
|
dictNo,
|
||||||
|
memoryNo,
|
||||||
|
};
|
||||||
|
|
||||||
|
const init = {
|
||||||
|
headers: {
|
||||||
|
"Content-type": "application/json",
|
||||||
|
},
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
};
|
||||||
|
|
||||||
|
return [url, init];
|
||||||
|
};
|
||||||
|
|
||||||
const genTencent = ({ text, from, to }) => {
|
const genTencent = ({ text, from, to }) => {
|
||||||
const data = {
|
const data = {
|
||||||
header: {
|
header: {
|
||||||
@@ -146,8 +196,8 @@ const genTencent = ({ text, from, to }) => {
|
|||||||
|
|
||||||
const genOpenAI = ({ text, from, to, url, key, prompt, model }) => {
|
const genOpenAI = ({ text, from, to, url, key, prompt, model }) => {
|
||||||
prompt = prompt
|
prompt = prompt
|
||||||
.replaceAll(PROMPT_PLACE_FROM, from)
|
.replaceAll(INPUT_PLACE_FROM, from)
|
||||||
.replaceAll(PROMPT_PLACE_TO, to);
|
.replaceAll(INPUT_PLACE_TO, to);
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
model,
|
model,
|
||||||
@@ -178,6 +228,37 @@ const genOpenAI = ({ text, from, to, url, key, prompt, model }) => {
|
|||||||
return [url, init];
|
return [url, init];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const genGemini = ({ text, from, to, url, key, prompt, model }) => {
|
||||||
|
prompt = prompt
|
||||||
|
.replaceAll(INPUT_PLACE_FROM, from)
|
||||||
|
.replaceAll(INPUT_PLACE_TO, to)
|
||||||
|
.replaceAll(INPUT_PLACE_TEXT, text);
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
contents: [
|
||||||
|
{
|
||||||
|
// role: "user",
|
||||||
|
parts: [
|
||||||
|
{
|
||||||
|
text: prompt,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const input = `${url}/${model}:generateContent?key=${key}`;
|
||||||
|
const init = {
|
||||||
|
headers: {
|
||||||
|
"Content-type": "application/json",
|
||||||
|
},
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
};
|
||||||
|
|
||||||
|
return [input, init];
|
||||||
|
};
|
||||||
|
|
||||||
const genCloudflareAI = ({ text, from, to, url, key }) => {
|
const genCloudflareAI = ({ text, from, to, url, key }) => {
|
||||||
const data = {
|
const data = {
|
||||||
text,
|
text,
|
||||||
@@ -197,7 +278,14 @@ const genCloudflareAI = ({ text, from, to, url, key }) => {
|
|||||||
return [url, init];
|
return [url, init];
|
||||||
};
|
};
|
||||||
|
|
||||||
const genCustom = ({ text, from, to, url, key }) => {
|
const genCustom = ({ text, from, to, url, key, customOption }) => {
|
||||||
|
const replaceInput = (str) =>
|
||||||
|
str
|
||||||
|
.replaceAll(INPUT_PLACE_URL, url)
|
||||||
|
.replaceAll(INPUT_PLACE_FROM, from)
|
||||||
|
.replaceAll(INPUT_PLACE_TO, to)
|
||||||
|
.replaceAll(INPUT_PLACE_TEXT, text.replaceAll(`"`, `\n`))
|
||||||
|
.replaceAll(INPUT_PLACE_KEY, key);
|
||||||
const data = {
|
const data = {
|
||||||
text,
|
text,
|
||||||
from,
|
from,
|
||||||
@@ -213,6 +301,23 @@ const genCustom = ({ text, from, to, url, key }) => {
|
|||||||
if (key) {
|
if (key) {
|
||||||
init.headers.Authorization = `Bearer ${key}`;
|
init.headers.Authorization = `Bearer ${key}`;
|
||||||
}
|
}
|
||||||
|
url = replaceInput(url);
|
||||||
|
|
||||||
|
if (customOption?.trim()) {
|
||||||
|
try {
|
||||||
|
const opt = JSON.parse(replaceInput(customOption));
|
||||||
|
opt.url && (url = opt.url);
|
||||||
|
opt.headers && (init.headers = opt.headers);
|
||||||
|
opt.method && (init.method = opt.method);
|
||||||
|
if (init.method === "GET") {
|
||||||
|
delete init.body;
|
||||||
|
} else {
|
||||||
|
opt.body && (init.body = JSON.stringify(opt.body));
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error(`custom option parse err: ${err}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return [url, init];
|
return [url, init];
|
||||||
};
|
};
|
||||||
@@ -224,6 +329,18 @@ const genCustom = ({ text, from, to, url, key }) => {
|
|||||||
*/
|
*/
|
||||||
export const newTransReq = ({ translator, text, from, to }, apiSetting) => {
|
export const newTransReq = ({ translator, text, from, to }, apiSetting) => {
|
||||||
const args = { text, from, to, ...apiSetting };
|
const args = { text, from, to, ...apiSetting };
|
||||||
|
|
||||||
|
switch (translator) {
|
||||||
|
case OPT_TRANS_DEEPL:
|
||||||
|
case OPT_TRANS_OPENAI:
|
||||||
|
case OPT_TRANS_GEMINI:
|
||||||
|
case OPT_TRANS_CLOUDFLAREAI:
|
||||||
|
case OPT_TRANS_NIUTRANS:
|
||||||
|
args.key = keyPick(translator, args.key);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
switch (translator) {
|
switch (translator) {
|
||||||
case OPT_TRANS_GOOGLE:
|
case OPT_TRANS_GOOGLE:
|
||||||
return genGoogle(args);
|
return genGoogle(args);
|
||||||
@@ -235,15 +352,23 @@ export const newTransReq = ({ translator, text, from, to }, apiSetting) => {
|
|||||||
return genDeeplFree(args);
|
return genDeeplFree(args);
|
||||||
case OPT_TRANS_DEEPLX:
|
case OPT_TRANS_DEEPLX:
|
||||||
return genDeeplX(args);
|
return genDeeplX(args);
|
||||||
|
case OPT_TRANS_NIUTRANS:
|
||||||
|
return genNiuTrans(args);
|
||||||
case OPT_TRANS_BAIDU:
|
case OPT_TRANS_BAIDU:
|
||||||
return genBaidu(args);
|
return genBaidu(args);
|
||||||
case OPT_TRANS_TENCENT:
|
case OPT_TRANS_TENCENT:
|
||||||
return genTencent(args);
|
return genTencent(args);
|
||||||
case OPT_TRANS_OPENAI:
|
case OPT_TRANS_OPENAI:
|
||||||
return genOpenAI(args);
|
return genOpenAI(args);
|
||||||
|
case OPT_TRANS_GEMINI:
|
||||||
|
return genGemini(args);
|
||||||
case OPT_TRANS_CLOUDFLAREAI:
|
case OPT_TRANS_CLOUDFLAREAI:
|
||||||
return genCloudflareAI(args);
|
return genCloudflareAI(args);
|
||||||
case OPT_TRANS_CUSTOMIZE:
|
case OPT_TRANS_CUSTOMIZE:
|
||||||
|
case OPT_TRANS_CUSTOMIZE_2:
|
||||||
|
case OPT_TRANS_CUSTOMIZE_3:
|
||||||
|
case OPT_TRANS_CUSTOMIZE_4:
|
||||||
|
case OPT_TRANS_CUSTOMIZE_5:
|
||||||
return genCustom(args);
|
return genCustom(args);
|
||||||
default:
|
default:
|
||||||
throw new Error(`[trans] translator: ${translator} not support`);
|
throw new Error(`[trans] translator: ${translator} not support`);
|
||||||
|
|||||||
@@ -6,13 +6,14 @@ import {
|
|||||||
OPT_STYLE_ALL,
|
OPT_STYLE_ALL,
|
||||||
OPT_LANGS_FROM,
|
OPT_LANGS_FROM,
|
||||||
OPT_LANGS_TO,
|
OPT_LANGS_TO,
|
||||||
|
OPT_TIMING_ALL,
|
||||||
GLOBLA_RULE,
|
GLOBLA_RULE,
|
||||||
DEFAULT_SUBRULES_LIST,
|
|
||||||
DEFAULT_OW_RULE,
|
|
||||||
} from "../config";
|
} from "../config";
|
||||||
import { loadOrFetchSubRules } from "./subRules";
|
import { loadOrFetchSubRules } from "./subRules";
|
||||||
import { getRulesWithDefault, setRules } from "./storage";
|
import { getRulesWithDefault, setRules } from "./storage";
|
||||||
import { trySyncRules } from "./sync";
|
import { trySyncRules } from "./sync";
|
||||||
|
import { FIXER_ALL } from "./webfix";
|
||||||
|
import { kissLog } from "./log";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据href匹配规则
|
* 根据href匹配规则
|
||||||
@@ -21,15 +22,10 @@ import { trySyncRules } from "./sync";
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const matchRule = async (
|
export const matchRule = async (
|
||||||
rules,
|
|
||||||
href,
|
href,
|
||||||
{
|
{ injectRules, subrulesList, owSubrule }
|
||||||
injectRules = true,
|
|
||||||
subrulesList = DEFAULT_SUBRULES_LIST,
|
|
||||||
owSubrule = DEFAULT_OW_RULE,
|
|
||||||
}
|
|
||||||
) => {
|
) => {
|
||||||
rules = [...rules];
|
const rules = await getRulesWithDefault();
|
||||||
if (injectRules) {
|
if (injectRules) {
|
||||||
try {
|
try {
|
||||||
const selectedSub = subrulesList.find((item) => item.selected);
|
const selectedSub = subrulesList.find((item) => item.selected);
|
||||||
@@ -54,19 +50,56 @@ export const matchRule = async (
|
|||||||
rules.splice(-1, 0, ...subRules);
|
rules.splice(-1, 0, ...subRules);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("[load injectRules]", err);
|
kissLog(err, "load injectRules");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 = {
|
||||||
|
...GLOBLA_RULE,
|
||||||
|
...(rules.find((r) => r.pattern === GLOBAL_KEY) || {}),
|
||||||
|
};
|
||||||
if (!rule) {
|
if (!rule) {
|
||||||
return globalRule;
|
return globalRule;
|
||||||
}
|
}
|
||||||
|
|
||||||
rule.selector = rule.selector?.trim() || globalRule.selector;
|
[
|
||||||
|
"selector",
|
||||||
|
"keepSelector",
|
||||||
|
"terms",
|
||||||
|
"selectStyle",
|
||||||
|
"parentStyle",
|
||||||
|
"injectJs",
|
||||||
|
"injectCss",
|
||||||
|
"fixerSelector",
|
||||||
|
].forEach((key) => {
|
||||||
|
if (!rule[key]?.trim()) {
|
||||||
|
rule[key] = globalRule[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
[
|
||||||
|
"translator",
|
||||||
|
"fromLang",
|
||||||
|
"toLang",
|
||||||
|
"transOpen",
|
||||||
|
"transOnly",
|
||||||
|
"transTiming",
|
||||||
|
"transTag",
|
||||||
|
"transTitle",
|
||||||
|
"detectRemote",
|
||||||
|
"fixerFunc",
|
||||||
|
].forEach((key) => {
|
||||||
|
if (rule[key] === undefined || rule[key] === GLOBAL_KEY) {
|
||||||
|
rule[key] = globalRule[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!rule.skipLangs || rule.skipLangs.length === 0) {
|
||||||
|
rule.skipLangs = globalRule.skipLangs;
|
||||||
|
}
|
||||||
if (rule.textStyle === GLOBAL_KEY) {
|
if (rule.textStyle === GLOBAL_KEY) {
|
||||||
rule.textStyle = globalRule.textStyle;
|
rule.textStyle = globalRule.textStyle;
|
||||||
rule.bgColor = globalRule.bgColor;
|
rule.bgColor = globalRule.bgColor;
|
||||||
@@ -75,11 +108,6 @@ export const matchRule = async (
|
|||||||
rule.bgColor = rule.bgColor?.trim() || globalRule.bgColor;
|
rule.bgColor = rule.bgColor?.trim() || globalRule.bgColor;
|
||||||
rule.textDiyStyle = rule.textDiyStyle?.trim() || globalRule.textDiyStyle;
|
rule.textDiyStyle = rule.textDiyStyle?.trim() || globalRule.textDiyStyle;
|
||||||
}
|
}
|
||||||
["translator", "fromLang", "toLang", "transOpen"].forEach((key) => {
|
|
||||||
if (rule[key] === GLOBAL_KEY) {
|
|
||||||
rule[key] = globalRule[key];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return rule;
|
return rule;
|
||||||
};
|
};
|
||||||
@@ -113,6 +141,12 @@ export const checkRules = (rules) => {
|
|||||||
({
|
({
|
||||||
pattern,
|
pattern,
|
||||||
selector,
|
selector,
|
||||||
|
keepSelector,
|
||||||
|
terms,
|
||||||
|
selectStyle,
|
||||||
|
parentStyle,
|
||||||
|
injectJs,
|
||||||
|
injectCss,
|
||||||
translator,
|
translator,
|
||||||
fromLang,
|
fromLang,
|
||||||
toLang,
|
toLang,
|
||||||
@@ -120,9 +154,23 @@ export const checkRules = (rules) => {
|
|||||||
transOpen,
|
transOpen,
|
||||||
bgColor,
|
bgColor,
|
||||||
textDiyStyle,
|
textDiyStyle,
|
||||||
|
transOnly,
|
||||||
|
transTiming,
|
||||||
|
transTag,
|
||||||
|
transTitle,
|
||||||
|
detectRemote,
|
||||||
|
skipLangs,
|
||||||
|
fixerSelector,
|
||||||
|
fixerFunc,
|
||||||
}) => ({
|
}) => ({
|
||||||
pattern: pattern.trim(),
|
pattern: pattern.trim(),
|
||||||
selector: type(selector) === "string" ? selector : "",
|
selector: type(selector) === "string" ? selector : "",
|
||||||
|
keepSelector: type(keepSelector) === "string" ? keepSelector : "",
|
||||||
|
terms: type(terms) === "string" ? terms : "",
|
||||||
|
selectStyle: type(selectStyle) === "string" ? selectStyle : "",
|
||||||
|
parentStyle: type(parentStyle) === "string" ? parentStyle : "",
|
||||||
|
injectJs: type(injectJs) === "string" ? injectJs : "",
|
||||||
|
injectCss: type(injectCss) === "string" ? injectCss : "",
|
||||||
bgColor: type(bgColor) === "string" ? bgColor : "",
|
bgColor: type(bgColor) === "string" ? bgColor : "",
|
||||||
textDiyStyle: type(textDiyStyle) === "string" ? textDiyStyle : "",
|
textDiyStyle: type(textDiyStyle) === "string" ? textDiyStyle : "",
|
||||||
translator: matchValue([GLOBAL_KEY, ...OPT_TRANS_ALL], translator),
|
translator: matchValue([GLOBAL_KEY, ...OPT_TRANS_ALL], translator),
|
||||||
@@ -130,6 +178,14 @@ export const checkRules = (rules) => {
|
|||||||
toLang: matchValue([GLOBAL_KEY, ...toLangs], toLang),
|
toLang: matchValue([GLOBAL_KEY, ...toLangs], toLang),
|
||||||
textStyle: matchValue([GLOBAL_KEY, ...OPT_STYLE_ALL], textStyle),
|
textStyle: matchValue([GLOBAL_KEY, ...OPT_STYLE_ALL], textStyle),
|
||||||
transOpen: matchValue([GLOBAL_KEY, "true", "false"], transOpen),
|
transOpen: matchValue([GLOBAL_KEY, "true", "false"], transOpen),
|
||||||
|
transOnly: matchValue([GLOBAL_KEY, "true", "false"], transOnly),
|
||||||
|
transTiming: matchValue([GLOBAL_KEY, ...OPT_TIMING_ALL], transTiming),
|
||||||
|
transTag: matchValue([GLOBAL_KEY, "span", "font"], transTag),
|
||||||
|
transTitle: matchValue([GLOBAL_KEY, "true", "false"], transTitle),
|
||||||
|
detectRemote: matchValue([GLOBAL_KEY, "true", "false"], detectRemote),
|
||||||
|
skipLangs: type(skipLangs) === "array" ? skipLangs : [],
|
||||||
|
fixerSelector: type(fixerSelector) === "string" ? fixerSelector : "",
|
||||||
|
fixerFunc: matchValue([GLOBAL_KEY, ...FIXER_ALL], fixerFunc),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -36,8 +36,8 @@ export const shortcutListener = (fn, target = document, timeout = 3000) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
target.addEventListener("keydown", handleKeydown);
|
target.addEventListener("keydown", handleKeydown, true);
|
||||||
target.addEventListener("keyup", handleKeyup);
|
target.addEventListener("keyup", handleKeyup, true);
|
||||||
return () => {
|
return () => {
|
||||||
if (timer) {
|
if (timer) {
|
||||||
clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
import {
|
import {
|
||||||
STOKEY_SETTING,
|
STOKEY_SETTING,
|
||||||
STOKEY_RULES,
|
STOKEY_RULES,
|
||||||
STOKEY_WFRULES,
|
|
||||||
STOKEY_WORDS,
|
STOKEY_WORDS,
|
||||||
STOKEY_FAB,
|
STOKEY_FAB,
|
||||||
STOKEY_SYNC,
|
STOKEY_SYNC,
|
||||||
STOKEY_MSAUTH,
|
STOKEY_MSAUTH,
|
||||||
STOKEY_BDAUTH,
|
STOKEY_BDAUTH,
|
||||||
STOKEY_RULESCACHE_PREFIX,
|
STOKEY_RULESCACHE_PREFIX,
|
||||||
STOKEY_WEBFIXCACHE_PREFIX,
|
|
||||||
DEFAULT_SETTING,
|
DEFAULT_SETTING,
|
||||||
DEFAULT_RULES,
|
DEFAULT_RULES,
|
||||||
DEFAULT_SYNC,
|
DEFAULT_SYNC,
|
||||||
@@ -16,6 +14,7 @@ import {
|
|||||||
} from "../config";
|
} from "../config";
|
||||||
import { isExt, isGm } from "./client";
|
import { isExt, isGm } from "./client";
|
||||||
import { browser } from "./browser";
|
import { browser } from "./browser";
|
||||||
|
import { kissLog } from "./log";
|
||||||
|
|
||||||
async function set(key, val) {
|
async function set(key, val) {
|
||||||
if (isExt) {
|
if (isExt) {
|
||||||
@@ -86,8 +85,10 @@ export const storage = {
|
|||||||
* 设置信息
|
* 设置信息
|
||||||
*/
|
*/
|
||||||
export const getSetting = () => getObj(STOKEY_SETTING);
|
export const getSetting = () => getObj(STOKEY_SETTING);
|
||||||
export const getSettingWithDefault = async () =>
|
export const getSettingWithDefault = async () => ({
|
||||||
(await getSetting()) || DEFAULT_SETTING;
|
...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);
|
||||||
|
|
||||||
@@ -99,14 +100,6 @@ export const getRulesWithDefault = async () =>
|
|||||||
(await getRules()) || DEFAULT_RULES;
|
(await getRules()) || DEFAULT_RULES;
|
||||||
export const setRules = (val) => setObj(STOKEY_RULES, val);
|
export const setRules = (val) => setObj(STOKEY_RULES, val);
|
||||||
|
|
||||||
/**
|
|
||||||
* 修复规则列表
|
|
||||||
*/
|
|
||||||
export const getWebfixRules = () => getObj(STOKEY_WFRULES);
|
|
||||||
export const getWebfixRulesWithDefault = async () =>
|
|
||||||
(await getWebfixRules()) || [];
|
|
||||||
export const setWebfixRules = (val) => setObj(STOKEY_WFRULES, val);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 词汇列表
|
* 词汇列表
|
||||||
*/
|
*/
|
||||||
@@ -123,14 +116,6 @@ 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位置
|
||||||
*/
|
*/
|
||||||
@@ -171,6 +156,6 @@ export const tryInitDefaultData = async () => {
|
|||||||
BUILTIN_RULES
|
BUILTIN_RULES
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("[init default]", err);
|
kissLog(err, "init default");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
import { apiFetch } from "../apis";
|
import { apiFetch } from "../apis";
|
||||||
import { checkRules } from "./rules";
|
import { checkRules } from "./rules";
|
||||||
import { isAllchar } from "./utils";
|
import { isAllchar } from "./utils";
|
||||||
import { syncWebfix } from "./webfix";
|
import { kissLog } from "./log";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新缓存同步时间
|
* 更新缓存同步时间
|
||||||
@@ -42,12 +42,12 @@ export const syncSubRules = async (url) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const syncAllSubRules = async (subrulesList) => {
|
export const syncAllSubRules = async (subrulesList) => {
|
||||||
for (let subrules of subrulesList) {
|
for (const subrules of subrulesList) {
|
||||||
try {
|
try {
|
||||||
await syncSubRules(subrules.url);
|
await syncSubRules(subrules.url);
|
||||||
await updateSyncDataCache(subrules.url);
|
await updateSyncDataCache(subrules.url);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(`[sync subrule error]: ${subrules.url}`, err);
|
kissLog(err, `sync subrule error: ${subrules.url}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -66,12 +66,9 @@ export const trySyncAllSubRules = async ({ subrulesList }) => {
|
|||||||
// 同步订阅规则
|
// 同步订阅规则
|
||||||
await syncAllSubRules(subrulesList);
|
await syncAllSubRules(subrulesList);
|
||||||
await updateSync({ subRulesSyncAt: now });
|
await updateSync({ subRulesSyncAt: now });
|
||||||
|
|
||||||
// 同步修复规则
|
|
||||||
await syncWebfix(process.env.REACT_APP_WEBFIXURL);
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("[try sync all subrules]", err);
|
kissLog(err, "try sync all subrules");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import {
|
|||||||
APP_LCNAME,
|
APP_LCNAME,
|
||||||
KV_SETTING_KEY,
|
KV_SETTING_KEY,
|
||||||
KV_RULES_KEY,
|
KV_RULES_KEY,
|
||||||
KV_WFRULES_KEY,
|
|
||||||
KV_WORDS_KEY,
|
KV_WORDS_KEY,
|
||||||
KV_RULES_SHARE_KEY,
|
KV_RULES_SHARE_KEY,
|
||||||
KV_SALT_SHARE,
|
KV_SALT_SHARE,
|
||||||
@@ -14,16 +13,15 @@ import {
|
|||||||
getSettingWithDefault,
|
getSettingWithDefault,
|
||||||
getRulesWithDefault,
|
getRulesWithDefault,
|
||||||
getWordsWithDefault,
|
getWordsWithDefault,
|
||||||
getWebfixRulesWithDefault,
|
|
||||||
setSetting,
|
setSetting,
|
||||||
setRules,
|
setRules,
|
||||||
setWebfixRules,
|
|
||||||
setWords,
|
setWords,
|
||||||
} from "./storage";
|
} from "./storage";
|
||||||
import { apiSyncData } from "../apis";
|
import { apiSyncData } from "../apis";
|
||||||
import { sha256, removeEndchar } from "./utils";
|
import { sha256, removeEndchar } from "./utils";
|
||||||
import { createClient, getPatcher } from "webdav";
|
import { createClient, getPatcher } from "webdav";
|
||||||
import { fetchApi } from "./fetch";
|
import { fetchApi } from "./fetch";
|
||||||
|
import { kissLog } from "./log";
|
||||||
|
|
||||||
getPatcher().patch("request", (opts) => {
|
getPatcher().patch("request", (opts) => {
|
||||||
return fetchApi({
|
return fetchApi({
|
||||||
@@ -118,7 +116,7 @@ export const trySyncSetting = async () => {
|
|||||||
try {
|
try {
|
||||||
await syncSetting();
|
await syncSetting();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("[sync setting]", err);
|
kissLog(err, "sync setting");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -137,26 +135,7 @@ export const trySyncRules = async () => {
|
|||||||
try {
|
try {
|
||||||
await syncRules();
|
await syncRules();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("[sync user rules]", err);
|
kissLog(err, "sync user rules");
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 同步修复规则
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const syncWebfixRules = async () => {
|
|
||||||
const res = await syncData(KV_WFRULES_KEY, getWebfixRulesWithDefault);
|
|
||||||
if (res?.isNew) {
|
|
||||||
await setWebfixRules(res.value);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const trySyncWebfixRules = async () => {
|
|
||||||
try {
|
|
||||||
await syncWebfixRules();
|
|
||||||
} catch (err) {
|
|
||||||
console.log("[sync user webfix rules]", err);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -175,7 +154,7 @@ export const trySyncWords = async () => {
|
|||||||
try {
|
try {
|
||||||
await syncWords();
|
await syncWords();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("[sync fav words]", err);
|
kissLog(err, "sync fav words");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -207,13 +186,11 @@ export const syncShareRules = async ({ rules, syncUrl, syncKey }) => {
|
|||||||
export const syncSettingAndRules = async () => {
|
export const syncSettingAndRules = async () => {
|
||||||
await syncSetting();
|
await syncSetting();
|
||||||
await syncRules();
|
await syncRules();
|
||||||
await syncWebfixRules();
|
|
||||||
await syncWords();
|
await syncWords();
|
||||||
};
|
};
|
||||||
|
|
||||||
export const trySyncSettingAndRules = async () => {
|
export const trySyncSettingAndRules = async () => {
|
||||||
await trySyncSetting();
|
await trySyncSetting();
|
||||||
await trySyncRules();
|
await trySyncRules();
|
||||||
await trySyncWebfixRules();
|
|
||||||
await trySyncWords();
|
await trySyncWords();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,106 +4,33 @@ import {
|
|||||||
TRANS_MIN_LENGTH,
|
TRANS_MIN_LENGTH,
|
||||||
TRANS_MAX_LENGTH,
|
TRANS_MAX_LENGTH,
|
||||||
MSG_TRANS_CURRULE,
|
MSG_TRANS_CURRULE,
|
||||||
|
MSG_INJECT_JS,
|
||||||
|
MSG_INJECT_CSS,
|
||||||
OPT_STYLE_DASHLINE,
|
OPT_STYLE_DASHLINE,
|
||||||
OPT_STYLE_FUZZY,
|
OPT_STYLE_FUZZY,
|
||||||
SHADOW_KEY,
|
SHADOW_KEY,
|
||||||
OPT_MOUSEKEY_DISABLE,
|
OPT_TIMING_PAGESCROLL,
|
||||||
OPT_MOUSEKEY_MOUSEOVER,
|
OPT_TIMING_PAGEOPEN,
|
||||||
DEFAULT_INPUT_RULE,
|
OPT_TIMING_MOUSEOVER,
|
||||||
DEFAULT_TRANS_APIS,
|
DEFAULT_TRANS_APIS,
|
||||||
DEFAULT_INPUT_SHORTCUT,
|
DEFAULT_FETCH_LIMIT,
|
||||||
OPT_LANGS_LIST,
|
DEFAULT_FETCH_INTERVAL,
|
||||||
} 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 {
|
import { debounce, genEventName } from "./utils";
|
||||||
debounce,
|
import { runFixer } from "./webfix";
|
||||||
genEventName,
|
|
||||||
removeEndchar,
|
|
||||||
matchInputStr,
|
|
||||||
sleep,
|
|
||||||
} from "./utils";
|
|
||||||
import { stepShortcutRegister } from "./shortcut";
|
|
||||||
import { apiTranslate } from "../apis";
|
import { apiTranslate } from "../apis";
|
||||||
import { tryDetectLang } from ".";
|
import { sendBgMsg } from "./msg";
|
||||||
import { loadingSvg } from "./svg";
|
import { isExt } from "./client";
|
||||||
|
import { injectInlineJs, injectInternalCss } from "./injector";
|
||||||
function isInputNode(node) {
|
import { kissLog } from "./log";
|
||||||
return node.nodeName === "INPUT" || node.nodeName === "TEXTAREA";
|
|
||||||
}
|
|
||||||
|
|
||||||
function isEditAbleNode(node) {
|
|
||||||
return node.hasAttribute("contenteditable");
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectContent(node) {
|
|
||||||
node.focus();
|
|
||||||
const range = document.createRange();
|
|
||||||
range.selectNodeContents(node);
|
|
||||||
|
|
||||||
const selection = window.getSelection();
|
|
||||||
selection.removeAllRanges();
|
|
||||||
selection.addRange(range);
|
|
||||||
}
|
|
||||||
|
|
||||||
function pasteContentEvent(node, text) {
|
|
||||||
node.focus();
|
|
||||||
const data = new DataTransfer();
|
|
||||||
data.setData("text/plain", text);
|
|
||||||
|
|
||||||
const event = new ClipboardEvent("paste", { clipboardData: data });
|
|
||||||
document.dispatchEvent(event);
|
|
||||||
data.clearData();
|
|
||||||
}
|
|
||||||
|
|
||||||
function pasteContentCommand(node, text) {
|
|
||||||
node.focus();
|
|
||||||
document.execCommand("insertText", false, text);
|
|
||||||
}
|
|
||||||
|
|
||||||
function collapseToEnd(node) {
|
|
||||||
node.focus();
|
|
||||||
const selection = window.getSelection();
|
|
||||||
selection.collapseToEnd();
|
|
||||||
}
|
|
||||||
|
|
||||||
function getNodeText(node) {
|
|
||||||
if (isInputNode(node)) {
|
|
||||||
return node.value;
|
|
||||||
}
|
|
||||||
return node.innerText || node.textContent || "";
|
|
||||||
}
|
|
||||||
|
|
||||||
function addLoading(node, loadingId) {
|
|
||||||
const div = document.createElement("div");
|
|
||||||
div.id = loadingId;
|
|
||||||
div.innerHTML = loadingSvg;
|
|
||||||
div.style.cssText = `
|
|
||||||
width: ${node.offsetWidth}px;
|
|
||||||
height: ${node.offsetHeight}px;
|
|
||||||
line-height: ${node.offsetHeight}px;
|
|
||||||
position: absolute;
|
|
||||||
text-align: center;
|
|
||||||
left: ${node.offsetLeft}px;
|
|
||||||
top: ${node.offsetTop}px;
|
|
||||||
z-index: 2147483647;
|
|
||||||
`;
|
|
||||||
node.offsetParent?.appendChild(div);
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeLoading(node, loadingId) {
|
|
||||||
const div = node.offsetParent.querySelector(`#${loadingId}`);
|
|
||||||
if (div) {
|
|
||||||
div.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 翻译类
|
* 翻译类
|
||||||
*/
|
*/
|
||||||
export class Translator {
|
export class Translator {
|
||||||
_rule = {};
|
_rule = {};
|
||||||
_inputRule = {};
|
|
||||||
_setting = {};
|
_setting = {};
|
||||||
_rootNodes = new Set();
|
_rootNodes = new Set();
|
||||||
_tranNodes = new Map();
|
_tranNodes = new Map();
|
||||||
@@ -125,14 +52,17 @@ export class Translator {
|
|||||||
];
|
];
|
||||||
_eventName = genEventName();
|
_eventName = genEventName();
|
||||||
_mouseoverNode = null;
|
_mouseoverNode = null;
|
||||||
|
_keepSelector = [null, null];
|
||||||
|
_terms = [];
|
||||||
|
_docTitle = "";
|
||||||
|
|
||||||
// 显示
|
// 显示
|
||||||
_interseObserver = new IntersectionObserver(
|
_interseObserver = new IntersectionObserver(
|
||||||
(intersections) => {
|
(entries, observer) => {
|
||||||
intersections.forEach((intersection) => {
|
entries.forEach((entry) => {
|
||||||
if (intersection.isIntersecting) {
|
if (entry.isIntersecting) {
|
||||||
this._render(intersection.target);
|
observer.unobserve(entry.target);
|
||||||
this._interseObserver.unobserve(intersection.target);
|
this._render(entry.target);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -176,22 +106,37 @@ export class Translator {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(rule, setting) {
|
_updatePool(translator) {
|
||||||
const { fetchInterval, fetchLimit } = setting;
|
if (!translator) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
fetchInterval = DEFAULT_FETCH_INTERVAL,
|
||||||
|
fetchLimit = DEFAULT_FETCH_LIMIT,
|
||||||
|
} = this._setting.transApis[translator] || {};
|
||||||
updateFetchPool(fetchInterval, fetchLimit);
|
updateFetchPool(fetchInterval, fetchLimit);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(rule, setting) {
|
||||||
this._overrideAttachShadow();
|
this._overrideAttachShadow();
|
||||||
|
|
||||||
this._setting = setting;
|
this._setting = setting;
|
||||||
this._rule = rule;
|
this._rule = rule;
|
||||||
|
|
||||||
|
this._keepSelector = (rule.keepSelector || "")
|
||||||
|
.split(SHADOW_KEY)
|
||||||
|
.map((item) => item.trim());
|
||||||
|
this._terms = (rule.terms || "")
|
||||||
|
.split(/\n|;/)
|
||||||
|
.map((item) => item.split(",").map((item) => item.trim()))
|
||||||
|
.filter(([term]) => Boolean(term));
|
||||||
|
|
||||||
|
this._updatePool(rule.translator);
|
||||||
|
|
||||||
if (rule.transOpen === "true") {
|
if (rule.transOpen === "true") {
|
||||||
this._register();
|
this._register();
|
||||||
}
|
}
|
||||||
|
|
||||||
this._inputRule = setting.inputRule || DEFAULT_INPUT_RULE;
|
|
||||||
if (this._inputRule.transOpen) {
|
|
||||||
this._registerInput();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get setting() {
|
get setting() {
|
||||||
@@ -225,6 +170,7 @@ export class Translator {
|
|||||||
|
|
||||||
updateRule = (obj) => {
|
updateRule = (obj) => {
|
||||||
this.rule = { ...this.rule, ...obj };
|
this.rule = { ...this.rule, ...obj };
|
||||||
|
this._updatePool(obj.translator);
|
||||||
};
|
};
|
||||||
|
|
||||||
toggle = () => {
|
toggle = () => {
|
||||||
@@ -245,11 +191,25 @@ export class Translator {
|
|||||||
this.rule = { ...this.rule, textStyle };
|
this.rule = { ...this.rule, textStyle };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
translateText = async (text) => {
|
||||||
|
const { translator, fromLang, toLang } = this._rule;
|
||||||
|
const apiSetting =
|
||||||
|
this._setting.transApis?.[translator] || DEFAULT_TRANS_APIS[translator];
|
||||||
|
const [trText] = await apiTranslate({
|
||||||
|
text,
|
||||||
|
translator,
|
||||||
|
fromLang,
|
||||||
|
toLang,
|
||||||
|
apiSetting,
|
||||||
|
});
|
||||||
|
return trText;
|
||||||
|
};
|
||||||
|
|
||||||
_querySelectorAll = (selector, node) => {
|
_querySelectorAll = (selector, node) => {
|
||||||
try {
|
try {
|
||||||
return Array.from(node.querySelectorAll(selector));
|
return Array.from(node.querySelectorAll(selector));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(`[querySelectorAll err]: ${selector}`);
|
kissLog(selector, "querySelectorAll err");
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
};
|
};
|
||||||
@@ -321,10 +281,26 @@ export class Translator {
|
|||||||
};
|
};
|
||||||
|
|
||||||
_register = () => {
|
_register = () => {
|
||||||
if (this._rule.fromLang === this._rule.toLang) {
|
const { fromLang, toLang, injectJs, injectCss, fixerSelector, fixerFunc } =
|
||||||
|
this._rule;
|
||||||
|
if (fromLang === toLang) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// webfix
|
||||||
|
if (fixerSelector && fixerFunc !== "-") {
|
||||||
|
runFixer(fixerSelector, fixerFunc);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注入用户JS/CSS
|
||||||
|
if (isExt) {
|
||||||
|
injectJs && sendBgMsg(MSG_INJECT_JS, injectJs);
|
||||||
|
injectCss && sendBgMsg(MSG_INJECT_CSS, injectCss);
|
||||||
|
} else {
|
||||||
|
injectJs && injectInlineJs(injectJs);
|
||||||
|
injectCss && injectInternalCss(injectCss);
|
||||||
|
}
|
||||||
|
|
||||||
// 搜索节点
|
// 搜索节点
|
||||||
this._queryNodes();
|
this._queryNodes();
|
||||||
|
|
||||||
@@ -338,13 +314,18 @@ export class Translator {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!this._setting.mouseKey ||
|
!this._rule.transTiming ||
|
||||||
this._setting.mouseKey === OPT_MOUSEKEY_DISABLE
|
this._rule.transTiming === OPT_TIMING_PAGESCROLL
|
||||||
) {
|
) {
|
||||||
// 监听节点显示
|
// 监听节点显示
|
||||||
this._tranNodes.forEach((_, node) => {
|
this._tranNodes.forEach((_, node) => {
|
||||||
this._interseObserver.observe(node);
|
this._interseObserver.observe(node);
|
||||||
});
|
});
|
||||||
|
} else if (this._rule.transTiming === OPT_TIMING_PAGEOPEN) {
|
||||||
|
// 全文直接翻译
|
||||||
|
this._tranNodes.forEach((_, node) => {
|
||||||
|
this._render(node);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
// 监听鼠标悬停
|
// 监听鼠标悬停
|
||||||
window.addEventListener("keydown", this._handleKeydown);
|
window.addEventListener("keydown", this._handleKeydown);
|
||||||
@@ -353,125 +334,15 @@ export class Translator {
|
|||||||
node.addEventListener("mouseleave", this._handleMouseout);
|
node.addEventListener("mouseleave", this._handleMouseout);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
_registerInput = () => {
|
// 翻译页面标题
|
||||||
const {
|
if (this._rule.transTitle === "true" && !this._docTitle) {
|
||||||
triggerShortcut: initTriggerShortcut,
|
const title = document.title;
|
||||||
translator,
|
this._docTitle = title;
|
||||||
fromLang,
|
this.translateText(title).then((trText) => {
|
||||||
toLang: initToLang,
|
document.title = `${trText} | ${title}`;
|
||||||
triggerCount: initTriggerCount,
|
});
|
||||||
triggerTime,
|
|
||||||
transSign,
|
|
||||||
} = this._inputRule;
|
|
||||||
const apiSetting =
|
|
||||||
this._setting.transApis?.[translator] || DEFAULT_TRANS_APIS[translator];
|
|
||||||
const { detectRemote } = this._setting;
|
|
||||||
|
|
||||||
let triggerShortcut = initTriggerShortcut;
|
|
||||||
let triggerCount = initTriggerCount;
|
|
||||||
if (triggerShortcut.length === 0) {
|
|
||||||
triggerShortcut = DEFAULT_INPUT_SHORTCUT;
|
|
||||||
triggerCount = 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
stepShortcutRegister(
|
|
||||||
triggerShortcut,
|
|
||||||
async () => {
|
|
||||||
let node = document.activeElement;
|
|
||||||
|
|
||||||
if (!node) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (node.shadowRoot) {
|
|
||||||
node = node.shadowRoot.activeElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isInputNode(node) && !isEditAbleNode(node)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let initText = getNodeText(node);
|
|
||||||
if (triggerShortcut.length === 1 && triggerShortcut[0].length === 1) {
|
|
||||||
// todo: remove multiple char
|
|
||||||
initText = removeEndchar(initText, triggerShortcut[0], triggerCount);
|
|
||||||
}
|
|
||||||
if (!initText.trim()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let text = initText;
|
|
||||||
let toLang = initToLang;
|
|
||||||
if (transSign) {
|
|
||||||
const res = matchInputStr(text, transSign);
|
|
||||||
if (res) {
|
|
||||||
let lang = res[1];
|
|
||||||
if (lang === "zh" || lang === "cn") {
|
|
||||||
lang = "zh-CN";
|
|
||||||
} else if (lang === "tw" || lang === "hk") {
|
|
||||||
lang = "zh-TW";
|
|
||||||
}
|
|
||||||
if (lang && OPT_LANGS_LIST.includes(lang)) {
|
|
||||||
toLang = lang;
|
|
||||||
}
|
|
||||||
text = res[2];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// console.log("input -->", text);
|
|
||||||
|
|
||||||
const loadingId = "kiss-" + genEventName();
|
|
||||||
try {
|
|
||||||
addLoading(node, loadingId);
|
|
||||||
|
|
||||||
const deLang = await tryDetectLang(text, detectRemote);
|
|
||||||
if (deLang && toLang.includes(deLang)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [trText, isSame] = await apiTranslate({
|
|
||||||
translator,
|
|
||||||
text,
|
|
||||||
fromLang,
|
|
||||||
toLang,
|
|
||||||
apiSetting,
|
|
||||||
});
|
|
||||||
if (!trText || isSame) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isInputNode(node)) {
|
|
||||||
node.value = trText;
|
|
||||||
node.dispatchEvent(
|
|
||||||
new Event("input", { bubbles: true, cancelable: true })
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
selectContent(node);
|
|
||||||
await sleep(200);
|
|
||||||
|
|
||||||
pasteContentEvent(node, trText);
|
|
||||||
await sleep(200);
|
|
||||||
|
|
||||||
// todo: use includes?
|
|
||||||
if (getNodeText(node).startsWith(initText)) {
|
|
||||||
pasteContentCommand(node, trText);
|
|
||||||
await sleep(100);
|
|
||||||
} else {
|
|
||||||
collapseToEnd(node);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.log("[translate input]", err.message);
|
|
||||||
} finally {
|
|
||||||
removeLoading(node, loadingId);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
triggerCount,
|
|
||||||
triggerTime
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
_handleMouseover = (e) => {
|
_handleMouseover = (e) => {
|
||||||
@@ -480,8 +351,8 @@ export class Translator {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const key = this._setting.mouseKey.slice(3);
|
const key = this._rule.transTiming.slice(3);
|
||||||
if (this._setting.mouseKey === OPT_MOUSEKEY_MOUSEOVER || e[key]) {
|
if (this._rule.transTiming === OPT_TIMING_MOUSEOVER || e[key]) {
|
||||||
e.target.removeEventListener("mouseenter", this._handleMouseover);
|
e.target.removeEventListener("mouseenter", this._handleMouseover);
|
||||||
e.target.removeEventListener("mouseleave", this._handleMouseout);
|
e.target.removeEventListener("mouseleave", this._handleMouseout);
|
||||||
this._render(e.target);
|
this._render(e.target);
|
||||||
@@ -501,7 +372,7 @@ export class Translator {
|
|||||||
|
|
||||||
_handleKeydown = (e) => {
|
_handleKeydown = (e) => {
|
||||||
// console.log("keydown", e);
|
// console.log("keydown", e);
|
||||||
const key = this._setting.mouseKey.slice(3);
|
const key = this._rule.transTiming.slice(3);
|
||||||
if (e[key] && this._mouseoverNode) {
|
if (e[key] && this._mouseoverNode) {
|
||||||
this._mouseoverNode.removeEventListener(
|
this._mouseoverNode.removeEventListener(
|
||||||
"mouseenter",
|
"mouseenter",
|
||||||
@@ -519,33 +390,45 @@ export class Translator {
|
|||||||
};
|
};
|
||||||
|
|
||||||
_unRegister = () => {
|
_unRegister = () => {
|
||||||
|
// 恢复页面标题
|
||||||
|
if (this._docTitle) {
|
||||||
|
document.title = this._docTitle;
|
||||||
|
this._docTitle = "";
|
||||||
|
}
|
||||||
|
|
||||||
// 解除节点变化监听
|
// 解除节点变化监听
|
||||||
this._mutaObserver.disconnect();
|
this._mutaObserver.disconnect();
|
||||||
|
|
||||||
// 解除节点显示监听
|
// 解除节点显示监听
|
||||||
// this._interseObserver.disconnect();
|
// this._interseObserver.disconnect();
|
||||||
|
|
||||||
if (
|
// 移除键盘监听
|
||||||
!this._setting.mouseKey ||
|
window.removeEventListener("keydown", this._handleKeydown);
|
||||||
this._setting.mouseKey === OPT_MOUSEKEY_DISABLE
|
|
||||||
) {
|
this._tranNodes.forEach((innerHTML, node) => {
|
||||||
// 解除节点显示监听
|
if (
|
||||||
this._tranNodes.forEach((_, node) => {
|
!this._rule.transTiming ||
|
||||||
|
this._rule.transTiming === OPT_TIMING_PAGESCROLL
|
||||||
|
) {
|
||||||
|
// 解除节点显示监听
|
||||||
this._interseObserver.unobserve(node);
|
this._interseObserver.unobserve(node);
|
||||||
// 移除已插入元素
|
} else if (this._rule.transTiming !== OPT_TIMING_PAGEOPEN) {
|
||||||
node.querySelector(APP_LCNAME)?.remove();
|
// 移除鼠标悬停监听
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// 移除鼠标悬停监听
|
|
||||||
window.removeEventListener("keydown", this._handleKeydown);
|
|
||||||
this._tranNodes.forEach((_, node) => {
|
|
||||||
// node.style.pointerEvents = "none";
|
// node.style.pointerEvents = "none";
|
||||||
node.removeEventListener("mouseenter", this._handleMouseover);
|
node.removeEventListener("mouseenter", this._handleMouseover);
|
||||||
node.removeEventListener("mouseleave", this._handleMouseout);
|
node.removeEventListener("mouseleave", this._handleMouseout);
|
||||||
// 移除已插入元素
|
}
|
||||||
|
|
||||||
|
// 移除/恢复元素
|
||||||
|
if (innerHTML && this._rule.transOnly === "true") {
|
||||||
|
node.innerHTML = innerHTML;
|
||||||
|
} else {
|
||||||
node.querySelector(APP_LCNAME)?.remove();
|
node.querySelector(APP_LCNAME)?.remove();
|
||||||
});
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
|
// 移除用户JS/CSS
|
||||||
|
this._removeInjector();
|
||||||
|
|
||||||
// 清空节点集合
|
// 清空节点集合
|
||||||
this._rootNodes.clear();
|
this._rootNodes.clear();
|
||||||
@@ -555,17 +438,36 @@ export class Translator {
|
|||||||
clearFetchPool();
|
clearFetchPool();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_removeInjector = () => {
|
||||||
|
document
|
||||||
|
.querySelectorAll(`[data-source^="KISS-Calendar"]`)
|
||||||
|
?.forEach((el) => el.remove());
|
||||||
|
};
|
||||||
|
|
||||||
_reTranslate = debounce(() => {
|
_reTranslate = debounce(() => {
|
||||||
if (this._rule.transOpen === "true") {
|
if (this._rule.transOpen === "true") {
|
||||||
|
window.removeEventListener("keydown", this._handleKeydown);
|
||||||
|
this._mutaObserver.disconnect();
|
||||||
|
this._interseObserver.disconnect();
|
||||||
|
this._removeInjector();
|
||||||
this._register();
|
this._register();
|
||||||
}
|
}
|
||||||
}, 500);
|
}, this._setting.transInterval);
|
||||||
|
|
||||||
|
_invalidLength = (q) =>
|
||||||
|
!q ||
|
||||||
|
q.length < (this._setting.minLength ?? TRANS_MIN_LENGTH) ||
|
||||||
|
q.length > (this._setting.maxLength ?? TRANS_MAX_LENGTH);
|
||||||
|
|
||||||
_render = (el) => {
|
_render = (el) => {
|
||||||
let traEl = el.querySelector(APP_LCNAME);
|
let traEl = el.querySelector(APP_LCNAME);
|
||||||
|
|
||||||
// 已翻译
|
// 已翻译
|
||||||
if (traEl) {
|
if (traEl) {
|
||||||
|
if (this._rule.transOnly === "true") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const preText = this._tranNodes.get(el);
|
const preText = this._tranNodes.get(el);
|
||||||
const curText = el.innerText.trim();
|
const curText = el.innerText.trim();
|
||||||
// const traText = traEl.innerText.trim();
|
// const traText = traEl.innerText.trim();
|
||||||
@@ -580,31 +482,75 @@ export class Translator {
|
|||||||
traEl.remove();
|
traEl.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
const q = el.innerText.trim();
|
let q = el.innerText.trim();
|
||||||
this._tranNodes.set(el, q);
|
if (this._rule.transOnly === "true") {
|
||||||
|
this._tranNodes.set(el, el.innerHTML);
|
||||||
|
} else {
|
||||||
|
this._tranNodes.set(el, q);
|
||||||
|
}
|
||||||
|
const keeps = [];
|
||||||
|
|
||||||
|
// 保留元素
|
||||||
|
const [matchSelector, subSelector] = this._keepSelector;
|
||||||
|
if (matchSelector || subSelector) {
|
||||||
|
let text = "";
|
||||||
|
el.childNodes.forEach((child) => {
|
||||||
|
if (
|
||||||
|
child.nodeType === 1 &&
|
||||||
|
((matchSelector && child.matches(matchSelector)) ||
|
||||||
|
(subSelector && child.querySelector(subSelector)))
|
||||||
|
) {
|
||||||
|
if (child.nodeName === "IMG") {
|
||||||
|
child.style.cssText += `width: ${child.width}px;`;
|
||||||
|
child.style.cssText += `height: ${child.height}px;`;
|
||||||
|
}
|
||||||
|
text += `[${keeps.length}]`;
|
||||||
|
keeps.push(child.outerHTML);
|
||||||
|
} else {
|
||||||
|
text += child.textContent;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (keeps.length > 0) {
|
||||||
|
// textContent会保留些无用的换行符,严重影响翻译质量
|
||||||
|
if (q.includes("\n")) {
|
||||||
|
q = text;
|
||||||
|
} else {
|
||||||
|
q = text.replaceAll("\n", " ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 太长或太短
|
// 太长或太短
|
||||||
if (
|
if (this._invalidLength(q.replace(/\[(\d+)\]/g, "").trim())) {
|
||||||
!q ||
|
|
||||||
q.length < (this._setting.minLength ?? TRANS_MIN_LENGTH) ||
|
|
||||||
q.length > (this._setting.maxLength ?? TRANS_MAX_LENGTH)
|
|
||||||
) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// console.log("---> ", q);
|
// 专业术语
|
||||||
|
if (this._terms.length > 0) {
|
||||||
|
for (const term of this._terms) {
|
||||||
|
const re = new RegExp(term[0], "g");
|
||||||
|
q = q.replace(re, (t) => {
|
||||||
|
const text = `[${keeps.length}]`;
|
||||||
|
keeps.push(`<i class="kiss-trem">${term[1] || t}</i>`);
|
||||||
|
return text;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
traEl = document.createElement(APP_LCNAME);
|
traEl = document.createElement(APP_LCNAME);
|
||||||
traEl.style.visibility = "visible";
|
traEl.style.visibility = "visible";
|
||||||
|
// if (this._rule.transOnly === "true") {
|
||||||
|
// el.innerHTML = "";
|
||||||
|
// }
|
||||||
|
const { selectStyle, parentStyle } = this._rule;
|
||||||
el.appendChild(traEl);
|
el.appendChild(traEl);
|
||||||
el.style.cssText +=
|
el.style.cssText += selectStyle;
|
||||||
"-webkit-line-clamp: unset; max-height: none; height: auto;";
|
|
||||||
if (el.parentElement) {
|
if (el.parentElement) {
|
||||||
el.parentElement.style.cssText +=
|
el.parentElement.style.cssText += parentStyle;
|
||||||
"-webkit-line-clamp: unset; max-height: none; height: auto;";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const root = createRoot(traEl);
|
const root = createRoot(traEl);
|
||||||
root.render(<Content q={q} translator={this} />);
|
root.render(<Content q={q} keeps={keeps} translator={this} $el={el} />);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -202,26 +202,20 @@ export const removeEndchar = (s, c, count = 1) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const matchInputStr = (str, sign) => {
|
export const matchInputStr = (str, sign) => {
|
||||||
let reg = /\/([\w-]+)\s+([^]+)/;
|
|
||||||
switch (sign) {
|
switch (sign) {
|
||||||
case "//":
|
case "//":
|
||||||
reg = /\/\/([\w-]+)\s+([^]+)/;
|
return str.match(/\/\/([\w-]+)\s+([^]+)/);
|
||||||
break;
|
|
||||||
case "\\":
|
case "\\":
|
||||||
reg = /\\([\w-]+)\s+([^]+)/;
|
return str.match(/\\([\w-]+)\s+([^]+)/);
|
||||||
break;
|
|
||||||
case "\\\\":
|
case "\\\\":
|
||||||
reg = /\\\\([\w-]+)\s+([^]+)/;
|
return str.match(/\\\\([\w-]+)\s+([^]+)/);
|
||||||
break;
|
|
||||||
case ">":
|
case ">":
|
||||||
reg = />([\w-]+)\s+([^]+)/;
|
return str.match(/>([\w-]+)\s+([^]+)/);
|
||||||
break;
|
|
||||||
case ">>":
|
case ">>":
|
||||||
reg = />>([\w-]+)\s+([^]+)/;
|
return str.match(/>>([\w-]+)\s+([^]+)/);
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
return str.match(reg);
|
return str.match(/\/([\w-]+)\s+([^]+)/);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -233,3 +227,16 @@ export const isValidWord = (str) => {
|
|||||||
const regex = /^[a-zA-Z-]+$/;
|
const regex = /^[a-zA-Z-]+$/;
|
||||||
return regex.test(str);
|
return regex.test(str);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* blob转为base64
|
||||||
|
* @param {*} blob
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const blobToBase64 = (blob) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onloadend = () => resolve(reader.result);
|
||||||
|
reader.readAsDataURL(blob);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,61 +1,24 @@
|
|||||||
import { isMatch } from "./utils";
|
|
||||||
import { getWebfix, setWebfix, getWebfixRulesWithDefault } from "./storage";
|
|
||||||
import { apiFetch } from "../apis";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修复程序类型
|
* 修复程序类型
|
||||||
*/
|
*/
|
||||||
const FIXER_BR = "br";
|
export const FIXER_NONE = "-";
|
||||||
const FIXER_BN = "bn";
|
export const FIXER_BR = "br";
|
||||||
const FIXER_BR_DIV = "brToDiv";
|
export const FIXER_BN = "bn";
|
||||||
const FIXER_BN_DIV = "bnToDiv";
|
export const FIXER_BR_DIV = "brToDiv";
|
||||||
const FIXER_FONTSIZE = "fontSize";
|
export const FIXER_BN_DIV = "bnToDiv";
|
||||||
|
|
||||||
export const FIXER_ALL = [
|
export const FIXER_ALL = [
|
||||||
|
FIXER_NONE,
|
||||||
FIXER_BR,
|
FIXER_BR,
|
||||||
FIXER_BN,
|
FIXER_BN,
|
||||||
FIXER_BR_DIV,
|
FIXER_BR_DIV,
|
||||||
FIXER_BN_DIV,
|
FIXER_BN_DIV,
|
||||||
FIXER_FONTSIZE,
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 需要修复的站点列表
|
|
||||||
* - pattern 匹配网址
|
|
||||||
* - selector 需要修复的选择器
|
|
||||||
* - rootSelector 需要监听的选择器,可留空
|
|
||||||
* - fixer 修复函数,可针对不同网址,选用不同修复函数
|
|
||||||
*/
|
|
||||||
const DEFAULT_SITES = [
|
|
||||||
{
|
|
||||||
pattern: "www.phoronix.com",
|
|
||||||
selector: ".content",
|
|
||||||
rootSelector: "",
|
|
||||||
fixer: FIXER_BR,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pattern: "t.me/s/",
|
|
||||||
selector: ".tgme_widget_message_text",
|
|
||||||
rootSelector: ".tgme_channel_history",
|
|
||||||
fixer: FIXER_BR,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pattern: "baidu.com",
|
|
||||||
selector: "html",
|
|
||||||
rootSelector: "",
|
|
||||||
fixer: FIXER_FONTSIZE,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pattern: "chat.openai.com",
|
|
||||||
selector: "div[data-testid^=conversation-turn] .items-start > div",
|
|
||||||
rootSelector: "",
|
|
||||||
fixer: FIXER_BN,
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修复过的标记
|
* 修复过的标记
|
||||||
*/
|
*/
|
||||||
const fixedSign = "kissfixed";
|
const fixedSign = "kiss-fixed";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 采用 `br` 换行网站的修复函数
|
* 采用 `br` 换行网站的修复函数
|
||||||
@@ -69,8 +32,8 @@ function brFixer(node, tag = "p") {
|
|||||||
}
|
}
|
||||||
node.setAttribute(fixedSign, "true");
|
node.setAttribute(fixedSign, "true");
|
||||||
|
|
||||||
var gapTags = ["BR", "WBR"];
|
const gapTags = ["BR", "WBR"];
|
||||||
var newlineTags = [
|
const newlineTags = [
|
||||||
"DIV",
|
"DIV",
|
||||||
"UL",
|
"UL",
|
||||||
"OL",
|
"OL",
|
||||||
@@ -85,9 +48,10 @@ function brFixer(node, tag = "p") {
|
|||||||
"HR",
|
"HR",
|
||||||
"PRE",
|
"PRE",
|
||||||
"TABLE",
|
"TABLE",
|
||||||
|
"BLOCKQUOTE",
|
||||||
];
|
];
|
||||||
|
|
||||||
var html = "";
|
let html = "";
|
||||||
node.childNodes.forEach(function (child, index) {
|
node.childNodes.forEach(function (child, index) {
|
||||||
if (index === 0) {
|
if (index === 0) {
|
||||||
html += `<${tag} class="kiss-p">`;
|
html += `<${tag} class="kiss-p">`;
|
||||||
@@ -99,8 +63,8 @@ function brFixer(node, tag = "p") {
|
|||||||
html += `</${tag}>${child.outerHTML}<${tag} class="kiss-p">`;
|
html += `</${tag}>${child.outerHTML}<${tag} class="kiss-p">`;
|
||||||
} else if (child.outerHTML) {
|
} else if (child.outerHTML) {
|
||||||
html += child.outerHTML;
|
html += child.outerHTML;
|
||||||
} else if (child.nodeValue) {
|
} else if (child.textContent) {
|
||||||
html += child.nodeValue;
|
html += child.textContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (index === node.childNodes.length - 1) {
|
if (index === node.childNodes.length - 1) {
|
||||||
@@ -134,25 +98,6 @@ function bnDivFixer(node) {
|
|||||||
return bnFixer(node, "div");
|
return bnFixer(node, "div");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 修复字体大小问题,如 baidu.com
|
|
||||||
* @param {*} node
|
|
||||||
*/
|
|
||||||
function fontSizeFixer(node) {
|
|
||||||
node.style.cssText += "font-size:1em;";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 修复程序映射
|
|
||||||
*/
|
|
||||||
const fixerMap = {
|
|
||||||
[FIXER_BR]: brFixer,
|
|
||||||
[FIXER_BN]: bnFixer,
|
|
||||||
[FIXER_BR_DIV]: brDivFixer,
|
|
||||||
[FIXER_BN_DIV]: bnDivFixer,
|
|
||||||
[FIXER_FONTSIZE]: fontSizeFixer,
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查找、监听节点,并执行修复函数
|
* 查找、监听节点,并执行修复函数
|
||||||
* @param {*} selector
|
* @param {*} selector
|
||||||
@@ -160,7 +105,7 @@ const fixerMap = {
|
|||||||
* @param {*} rootSelector
|
* @param {*} rootSelector
|
||||||
*/
|
*/
|
||||||
function run(selector, fixer, rootSelector) {
|
function run(selector, fixer, rootSelector) {
|
||||||
var mutaObserver = new MutationObserver(function (mutations) {
|
const mutaObserver = new MutationObserver(function (mutations) {
|
||||||
mutations.forEach(function (mutation) {
|
mutations.forEach(function (mutation) {
|
||||||
mutation.addedNodes.forEach(function (addNode) {
|
mutation.addedNodes.forEach(function (addNode) {
|
||||||
if (addNode && addNode.querySelectorAll) {
|
if (addNode && addNode.querySelectorAll) {
|
||||||
@@ -172,7 +117,7 @@ function run(selector, fixer, rootSelector) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
var rootNodes = [document];
|
let rootNodes = [document];
|
||||||
if (rootSelector) {
|
if (rootSelector) {
|
||||||
rootNodes = document.querySelectorAll(rootSelector);
|
rootNodes = document.querySelectorAll(rootSelector);
|
||||||
}
|
}
|
||||||
@@ -189,57 +134,25 @@ function run(selector, fixer, rootSelector) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 同步远程数据
|
* 修复程序映射
|
||||||
* @param {*} url
|
|
||||||
* @returns
|
|
||||||
*/
|
*/
|
||||||
export const syncWebfix = async (url) => {
|
const fixerMap = {
|
||||||
const sites = await apiFetch(url);
|
[FIXER_BR]: brFixer,
|
||||||
await setWebfix(url, sites);
|
[FIXER_BN]: bnFixer,
|
||||||
return sites;
|
[FIXER_BR_DIV]: brDivFixer,
|
||||||
|
[FIXER_BN_DIV]: bnDivFixer,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从缓存或远程加载修复站点
|
* 执行fixer
|
||||||
* @param {*} url
|
* @param {*} param0
|
||||||
* @returns
|
|
||||||
*/
|
*/
|
||||||
export const loadOrFetchWebfix = async (url) => {
|
export function runFixer(selector, fixer = "-", rootSelector) {
|
||||||
try {
|
try {
|
||||||
let sites = await getWebfix(url);
|
if (Object.keys(fixerMap).includes(fixer)) {
|
||||||
if (sites?.length) {
|
run(selector, fixerMap[fixer], rootSelector);
|
||||||
return sites;
|
|
||||||
}
|
|
||||||
return syncWebfix(url);
|
|
||||||
} catch (err) {
|
|
||||||
console.log("[load webfix]", err.message);
|
|
||||||
return DEFAULT_SITES;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 匹配站点
|
|
||||||
*/
|
|
||||||
export async function runWebfix({ injectWebfix }) {
|
|
||||||
try {
|
|
||||||
if (!injectWebfix) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const href = document.location.href;
|
|
||||||
const userSites = await getWebfixRulesWithDefault();
|
|
||||||
const subSites = await loadOrFetchWebfix(process.env.REACT_APP_WEBFIXURL);
|
|
||||||
const sites = [...userSites, ...subSites];
|
|
||||||
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.rootSelector);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`[kiss-webfix]: ${err.message}`);
|
console.error(`[kiss-webfix run]: ${err.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { BUILTIN_RULES } from "./config/rules";
|
|||||||
(() => {
|
(() => {
|
||||||
// rules
|
// rules
|
||||||
try {
|
try {
|
||||||
const data = JSON.stringify(BUILTIN_RULES, null, " ");
|
const data = JSON.stringify(BUILTIN_RULES, null, 2);
|
||||||
const file = path.resolve(
|
const file = path.resolve(
|
||||||
__dirname,
|
__dirname,
|
||||||
"../build/web/kiss-translator-rules.json"
|
"../build/web/kiss-translator-rules.json"
|
||||||
|
|||||||
@@ -1,82 +1,3 @@
|
|||||||
import { getSettingWithDefault } from "./libs/storage";
|
import { run } from "./common";
|
||||||
import { trySyncAllSubRules } from "./libs/subRules";
|
|
||||||
import { isIframe } from "./libs/iframe";
|
|
||||||
import { handlePing, injectScript } from "./libs/gm";
|
|
||||||
import { genEventName } from "./libs/utils";
|
|
||||||
import { runWebfix } from "./libs/webfix";
|
|
||||||
import {
|
|
||||||
runIframe,
|
|
||||||
runTranslator,
|
|
||||||
showFab,
|
|
||||||
showTransbox,
|
|
||||||
windowListener,
|
|
||||||
showErr,
|
|
||||||
touchOperation,
|
|
||||||
} from "./common";
|
|
||||||
|
|
||||||
function runSettingPage() {
|
run(true);
|
||||||
if (GM?.info?.script?.grant?.includes("unsafeWindow")) {
|
|
||||||
unsafeWindow.GM = GM;
|
|
||||||
unsafeWindow.APP_INFO = {
|
|
||||||
name: process.env.REACT_APP_NAME,
|
|
||||||
version: process.env.REACT_APP_VERSION,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
const ping = genEventName();
|
|
||||||
window.addEventListener(ping, handlePing);
|
|
||||||
// window.eval(`(${injectScript})("${ping}")`); // eslint-disable-line
|
|
||||||
const script = document.createElement("script");
|
|
||||||
script.textContent = `(${injectScript})("${ping}")`;
|
|
||||||
document.head.append(script);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 入口函数
|
|
||||||
*/
|
|
||||||
(async () => {
|
|
||||||
try {
|
|
||||||
// 设置页面
|
|
||||||
if (
|
|
||||||
document.location.href.includes(process.env.REACT_APP_OPTIONSPAGE_DEV) ||
|
|
||||||
document.location.href.includes(process.env.REACT_APP_OPTIONSPAGE) ||
|
|
||||||
document.location.href.includes(process.env.REACT_APP_OPTIONSPAGE2)
|
|
||||||
) {
|
|
||||||
runSettingPage();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 读取设置信息
|
|
||||||
const setting = await getSettingWithDefault();
|
|
||||||
|
|
||||||
// 适配iframe
|
|
||||||
if (isIframe) {
|
|
||||||
runIframe(setting);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 不规范网页修复
|
|
||||||
await runWebfix(setting);
|
|
||||||
|
|
||||||
// 翻译网页
|
|
||||||
const { translator, rule } = await runTranslator(setting);
|
|
||||||
|
|
||||||
// 监听消息
|
|
||||||
windowListener(rule);
|
|
||||||
|
|
||||||
// 划词翻译
|
|
||||||
showTransbox(setting);
|
|
||||||
|
|
||||||
// 浮球按钮
|
|
||||||
await showFab(translator);
|
|
||||||
|
|
||||||
// 触屏操作
|
|
||||||
touchOperation(translator);
|
|
||||||
|
|
||||||
// 同步订阅规则
|
|
||||||
await trySyncAllSubRules(setting);
|
|
||||||
} catch (err) {
|
|
||||||
console.error("[KISS-Translator]", err);
|
|
||||||
showErr(err.message);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
} from "../../config";
|
} from "../../config";
|
||||||
import { shortcutRegister } from "../../libs/shortcut";
|
import { shortcutRegister } from "../../libs/shortcut";
|
||||||
import { sendIframeMsg } from "../../libs/iframe";
|
import { sendIframeMsg } from "../../libs/iframe";
|
||||||
|
import { kissLog } from "../../libs/log";
|
||||||
|
|
||||||
export default function Action({ translator, fab }) {
|
export default function Action({ translator, fab }) {
|
||||||
const fabWidth = 40;
|
const fabWidth = 40;
|
||||||
@@ -95,40 +96,42 @@ export default function Action({ translator, fab }) {
|
|||||||
// 注册菜单
|
// 注册菜单
|
||||||
try {
|
try {
|
||||||
const menuCommandIds = [];
|
const menuCommandIds = [];
|
||||||
menuCommandIds.push(
|
const { contextMenuType } = translator.setting;
|
||||||
GM.registerMenuCommand(
|
contextMenuType !== 0 &&
|
||||||
"Toggle Translate (Alt+q)",
|
menuCommandIds.push(
|
||||||
(event) => {
|
GM.registerMenuCommand(
|
||||||
translator.toggle();
|
"Toggle Translate",
|
||||||
sendIframeMsg(MSG_TRANS_TOGGLE);
|
(event) => {
|
||||||
setShowPopup(false);
|
translator.toggle();
|
||||||
},
|
sendIframeMsg(MSG_TRANS_TOGGLE);
|
||||||
"Q"
|
setShowPopup(false);
|
||||||
),
|
},
|
||||||
GM.registerMenuCommand(
|
"Q"
|
||||||
"Toggle Style (Alt+c)",
|
),
|
||||||
(event) => {
|
GM.registerMenuCommand(
|
||||||
translator.toggleStyle();
|
"Toggle Style",
|
||||||
sendIframeMsg(MSG_TRANS_TOGGLE_STYLE);
|
(event) => {
|
||||||
setShowPopup(false);
|
translator.toggleStyle();
|
||||||
},
|
sendIframeMsg(MSG_TRANS_TOGGLE_STYLE);
|
||||||
"C"
|
setShowPopup(false);
|
||||||
),
|
},
|
||||||
GM.registerMenuCommand(
|
"C"
|
||||||
"Open Menu (Alt+k)",
|
),
|
||||||
(event) => {
|
GM.registerMenuCommand(
|
||||||
setShowPopup((pre) => !pre);
|
"Open Menu",
|
||||||
},
|
(event) => {
|
||||||
"K"
|
setShowPopup((pre) => !pre);
|
||||||
),
|
},
|
||||||
GM.registerMenuCommand(
|
"K"
|
||||||
"Open Setting (Alt+o)",
|
),
|
||||||
(event) => {
|
GM.registerMenuCommand(
|
||||||
window.open(process.env.REACT_APP_OPTIONSPAGE, "_blank");
|
"Open Setting",
|
||||||
},
|
(event) => {
|
||||||
"O"
|
window.open(process.env.REACT_APP_OPTIONSPAGE, "_blank");
|
||||||
)
|
},
|
||||||
);
|
"O"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
menuCommandIds.forEach((id) => {
|
menuCommandIds.forEach((id) => {
|
||||||
@@ -136,7 +139,7 @@ export default function Action({ translator, fab }) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("[registerMenuCommand]", err);
|
kissLog(err, "registerMenuCommand");
|
||||||
}
|
}
|
||||||
}, [translator]);
|
}, [translator]);
|
||||||
|
|
||||||
@@ -215,7 +218,12 @@ export default function Action({ translator, fab }) {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TranslateIcon />
|
<TranslateIcon
|
||||||
|
sx={{
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</Fab>
|
</Fab>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { loadingSvg } from "../../libs/svg";
|
|||||||
|
|
||||||
export default function LoadingIcon() {
|
export default function LoadingIcon() {
|
||||||
return (
|
return (
|
||||||
<div
|
<span
|
||||||
style={{
|
style={{
|
||||||
display: "inline-block",
|
display: "inline-block",
|
||||||
width: "1.2em",
|
width: "1.2em",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect, useMemo } from "react";
|
||||||
import LoadingIcon from "./LoadingIcon";
|
import LoadingIcon from "./LoadingIcon";
|
||||||
import {
|
import {
|
||||||
OPT_STYLE_LINE,
|
OPT_STYLE_LINE,
|
||||||
@@ -7,98 +7,88 @@ import {
|
|||||||
OPT_STYLE_WAVYLINE,
|
OPT_STYLE_WAVYLINE,
|
||||||
OPT_STYLE_FUZZY,
|
OPT_STYLE_FUZZY,
|
||||||
OPT_STYLE_HIGHLIGHT,
|
OPT_STYLE_HIGHLIGHT,
|
||||||
|
OPT_STYLE_BLOCKQUOTE,
|
||||||
OPT_STYLE_DIY,
|
OPT_STYLE_DIY,
|
||||||
DEFAULT_COLOR,
|
DEFAULT_COLOR,
|
||||||
MSG_TRANS_CURRULE,
|
MSG_TRANS_CURRULE,
|
||||||
TRANS_NEWLINE_LENGTH,
|
|
||||||
} from "../../config";
|
} from "../../config";
|
||||||
import { useTranslate } from "../../hooks/Translate";
|
import { useTranslate } from "../../hooks/Translate";
|
||||||
import { styled } from "@mui/material/styles";
|
import { styled, css } from "@mui/material/styles";
|
||||||
|
import { APP_LCNAME } from "../../config";
|
||||||
|
|
||||||
const LineSpan = styled("span")`
|
const LINE_STYLES = {
|
||||||
opacity: 0.6;
|
[OPT_STYLE_LINE]: "solid",
|
||||||
-webkit-opacity: 0.6;
|
[OPT_STYLE_DOTLINE]: "dotted",
|
||||||
text-decoration-line: underline;
|
[OPT_STYLE_DASHLINE]: "dashed",
|
||||||
text-decoration-style: ${(props) => props.$lineStyle};
|
[OPT_STYLE_WAVYLINE]: "wavy",
|
||||||
text-decoration-color: ${(props) => props.$lineColor};
|
};
|
||||||
text-decoration-thickness: 2px;
|
|
||||||
text-underline-offset: 0.3em;
|
const StyledSpan = styled("span")`
|
||||||
-webkit-text-decoration-line: underline;
|
${({ textStyle, textDiyStyle, bgColor }) => {
|
||||||
-webkit-text-decoration-style: ${(props) => props.$lineStyle};
|
switch (textStyle) {
|
||||||
-webkit-text-decoration-color: ${(props) => props.$lineColor};
|
case OPT_STYLE_LINE: // 下划线
|
||||||
-webkit-text-decoration-thickness: 2px;
|
case OPT_STYLE_DOTLINE: // 点状线
|
||||||
-webkit-text-underline-offset: 0.3em;
|
case OPT_STYLE_DASHLINE: // 虚线
|
||||||
&:hover {
|
case OPT_STYLE_WAVYLINE: // 波浪线
|
||||||
opacity: 1;
|
return css`
|
||||||
-webkit-opacity: 1;
|
opacity: 0.6;
|
||||||
}
|
-webkit-opacity: 0.6;
|
||||||
|
text-decoration-line: underline;
|
||||||
|
text-decoration-style: ${LINE_STYLES[textStyle]};
|
||||||
|
text-decoration-color: ${bgColor};
|
||||||
|
text-decoration-thickness: 2px;
|
||||||
|
text-underline-offset: 0.3em;
|
||||||
|
-webkit-text-decoration-line: underline;
|
||||||
|
-webkit-text-decoration-style: ${LINE_STYLES[textStyle]};
|
||||||
|
-webkit-text-decoration-color: ${bgColor};
|
||||||
|
-webkit-text-decoration-thickness: 2px;
|
||||||
|
-webkit-text-underline-offset: 0.3em;
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
-webkit-opacity: 1;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
case OPT_STYLE_FUZZY: // 模糊
|
||||||
|
return css`
|
||||||
|
filter: blur(0.2em);
|
||||||
|
-webkit-filter: blur(0.2em);
|
||||||
|
&:hover {
|
||||||
|
filter: none;
|
||||||
|
-webkit-filter: none;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
case OPT_STYLE_HIGHLIGHT: // 高亮
|
||||||
|
return css`
|
||||||
|
color: #fff;
|
||||||
|
background-color: ${bgColor || DEFAULT_COLOR};
|
||||||
|
`;
|
||||||
|
case OPT_STYLE_BLOCKQUOTE: // 引用
|
||||||
|
return css`
|
||||||
|
opacity: 0.6;
|
||||||
|
-webkit-opacity: 0.6;
|
||||||
|
display: block;
|
||||||
|
padding: 0 0.75em;
|
||||||
|
border-left: 0.25em solid ${bgColor || DEFAULT_COLOR};
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
-webkit-opacity: 1;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
case OPT_STYLE_DIY: // 自定义
|
||||||
|
return textDiyStyle;
|
||||||
|
default:
|
||||||
|
return ``;
|
||||||
|
}
|
||||||
|
}}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const FuzzySpan = styled("span")`
|
export default function Content({ q, keeps, translator, $el }) {
|
||||||
filter: blur(0.2em);
|
|
||||||
-webkit-filter: blur(0.2em);
|
|
||||||
&:hover {
|
|
||||||
filter: none;
|
|
||||||
-webkit-filter: none;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const HighlightSpan = styled("span")`
|
|
||||||
color: #fff;
|
|
||||||
background-color: ${(props) => props.$bgColor};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const DiySpan = styled("span")`
|
|
||||||
${(props) => props.$diyStyle}
|
|
||||||
`;
|
|
||||||
|
|
||||||
function StyledSpan({ textStyle, textDiyStyle, bgColor, children }) {
|
|
||||||
switch (textStyle) {
|
|
||||||
case OPT_STYLE_LINE: // 下划线
|
|
||||||
return (
|
|
||||||
<LineSpan $lineStyle="solid" $lineColor={bgColor}>
|
|
||||||
{children}
|
|
||||||
</LineSpan>
|
|
||||||
);
|
|
||||||
case OPT_STYLE_DOTLINE: // 点状线
|
|
||||||
return (
|
|
||||||
<LineSpan $lineStyle="dotted" $lineColor={bgColor}>
|
|
||||||
{children}
|
|
||||||
</LineSpan>
|
|
||||||
);
|
|
||||||
case OPT_STYLE_DASHLINE: // 虚线
|
|
||||||
return (
|
|
||||||
<LineSpan $lineStyle="dashed" $lineColor={bgColor}>
|
|
||||||
{children}
|
|
||||||
</LineSpan>
|
|
||||||
);
|
|
||||||
case OPT_STYLE_WAVYLINE: // 波浪线
|
|
||||||
return (
|
|
||||||
<LineSpan $lineStyle="wavy" $lineColor={bgColor}>
|
|
||||||
{children}
|
|
||||||
</LineSpan>
|
|
||||||
);
|
|
||||||
case OPT_STYLE_FUZZY: // 模糊
|
|
||||||
return <FuzzySpan>{children}</FuzzySpan>;
|
|
||||||
case OPT_STYLE_HIGHLIGHT: // 高亮
|
|
||||||
return (
|
|
||||||
<HighlightSpan $bgColor={bgColor || DEFAULT_COLOR}>
|
|
||||||
{children}
|
|
||||||
</HighlightSpan>
|
|
||||||
);
|
|
||||||
case OPT_STYLE_DIY: // 自定义
|
|
||||||
return <DiySpan $diyStyle={textDiyStyle}>{children}</DiySpan>;
|
|
||||||
default:
|
|
||||||
return <span>{children}</span>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Content({ q, translator }) {
|
|
||||||
const [rule, setRule] = useState(translator.rule);
|
const [rule, setRule] = useState(translator.rule);
|
||||||
const { text, sameLang, loading } = useTranslate(q, rule, translator.setting);
|
const { text, sameLang, loading } = useTranslate(q, rule, translator.setting);
|
||||||
const { textStyle, bgColor = "", textDiyStyle = "" } = rule;
|
const { transOpen, textStyle, bgColor, textDiyStyle, transOnly, transTag } =
|
||||||
|
rule;
|
||||||
|
|
||||||
const { newlineLength = TRANS_NEWLINE_LENGTH } = translator.setting;
|
const { newlineLength } = translator.setting;
|
||||||
|
|
||||||
const handleKissEvent = (e) => {
|
const handleKissEvent = (e) => {
|
||||||
const { action, args } = e.detail;
|
const { action, args } = e.detail;
|
||||||
@@ -117,27 +107,66 @@ export default function Content({ q, translator }) {
|
|||||||
};
|
};
|
||||||
}, [translator.eventName]);
|
}, [translator.eventName]);
|
||||||
|
|
||||||
|
const gap = useMemo(() => {
|
||||||
|
if (transOnly === "true") {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return q.length >= newlineLength ? <br /> : " ";
|
||||||
|
}, [q, transOnly, newlineLength]);
|
||||||
|
|
||||||
|
const styles = useMemo(
|
||||||
|
() => ({
|
||||||
|
textStyle,
|
||||||
|
textDiyStyle,
|
||||||
|
bgColor,
|
||||||
|
as: transTag,
|
||||||
|
}),
|
||||||
|
[textStyle, textDiyStyle, bgColor, transTag]
|
||||||
|
);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{q.length >= newlineLength ? <br /> : " "}
|
{gap}
|
||||||
<LoadingIcon />
|
<LoadingIcon />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (text && !sameLang) {
|
if (!text || sameLang) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
transOnly === "true" &&
|
||||||
|
transOpen === "true" &&
|
||||||
|
$el.querySelector(APP_LCNAME)
|
||||||
|
) {
|
||||||
|
Array.from($el.childNodes).forEach((el) => {
|
||||||
|
if (el.localName !== APP_LCNAME) {
|
||||||
|
el.remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keeps.length > 0) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{q.length >= newlineLength ? <br /> : " "}
|
{gap}
|
||||||
<StyledSpan
|
<StyledSpan
|
||||||
textStyle={textStyle}
|
{...styles}
|
||||||
textDiyStyle={textDiyStyle}
|
dangerouslySetInnerHTML={{
|
||||||
bgColor={bgColor}
|
__html: text.replace(/\[(\d+)\]/g, (_, p) => keeps[parseInt(p)]),
|
||||||
>
|
}}
|
||||||
{text}
|
/>
|
||||||
</StyledSpan>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{gap}
|
||||||
|
<StyledSpan {...styles}>{text}</StyledSpan>
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,23 @@
|
|||||||
import Stack from "@mui/material/Stack";
|
import Stack from "@mui/material/Stack";
|
||||||
import TextField from "@mui/material/TextField";
|
import TextField from "@mui/material/TextField";
|
||||||
import Button from "@mui/material/Button";
|
import Button from "@mui/material/Button";
|
||||||
import CircularProgress from "@mui/material/CircularProgress";
|
import LoadingButton from "@mui/lab/LoadingButton";
|
||||||
import {
|
import {
|
||||||
OPT_TRANS_ALL,
|
OPT_TRANS_ALL,
|
||||||
OPT_TRANS_MICROSOFT,
|
OPT_TRANS_MICROSOFT,
|
||||||
|
OPT_TRANS_DEEPL,
|
||||||
OPT_TRANS_DEEPLFREE,
|
OPT_TRANS_DEEPLFREE,
|
||||||
OPT_TRANS_BAIDU,
|
OPT_TRANS_BAIDU,
|
||||||
OPT_TRANS_TENCENT,
|
OPT_TRANS_TENCENT,
|
||||||
OPT_TRANS_OPENAI,
|
OPT_TRANS_OPENAI,
|
||||||
|
OPT_TRANS_GEMINI,
|
||||||
|
OPT_TRANS_CLOUDFLAREAI,
|
||||||
OPT_TRANS_CUSTOMIZE,
|
OPT_TRANS_CUSTOMIZE,
|
||||||
|
OPT_TRANS_NIUTRANS,
|
||||||
URL_KISS_PROXY,
|
URL_KISS_PROXY,
|
||||||
|
URL_NIUTRANS_REG,
|
||||||
|
DEFAULT_FETCH_LIMIT,
|
||||||
|
DEFAULT_FETCH_INTERVAL,
|
||||||
} from "../../config";
|
} from "../../config";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useI18n } from "../../hooks/I18n";
|
import { useI18n } from "../../hooks/I18n";
|
||||||
@@ -25,6 +32,7 @@ import { useApi } from "../../hooks/Api";
|
|||||||
import { apiTranslate } from "../../apis";
|
import { apiTranslate } from "../../apis";
|
||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
import Link from "@mui/material/Link";
|
import Link from "@mui/material/Link";
|
||||||
|
import { limitNumber } from "../../libs/utils";
|
||||||
|
|
||||||
function TestButton({ translator, api }) {
|
function TestButton({ translator, api }) {
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
@@ -47,17 +55,33 @@ function TestButton({ translator, api }) {
|
|||||||
alert.success(i18n("test_success"));
|
alert.success(i18n("test_success"));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// alert.error(`${i18n("test_failed")}: ${err.message}`);
|
// alert.error(`${i18n("test_failed")}: ${err.message}`);
|
||||||
|
let msg = err.message;
|
||||||
|
try {
|
||||||
|
msg = JSON.stringify(JSON.parse(err.message), null, 2);
|
||||||
|
} catch (err) {
|
||||||
|
// skip
|
||||||
|
}
|
||||||
alert.error(
|
alert.error(
|
||||||
<>
|
<>
|
||||||
<div>{`${i18n("test_failed")}: ${err.message}`}</div>
|
<div>{i18n("test_failed")}</div>
|
||||||
<pre
|
{msg === err.message ? (
|
||||||
style={{
|
<div
|
||||||
maxWidth: 400,
|
style={{
|
||||||
overflow: "auto",
|
maxWidth: 400,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{JSON.stringify(err.cause || {}, null, 2)}
|
{msg}
|
||||||
</pre>
|
</div>
|
||||||
|
) : (
|
||||||
|
<pre
|
||||||
|
style={{
|
||||||
|
maxWidth: 400,
|
||||||
|
overflow: "auto",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{msg}
|
||||||
|
</pre>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -65,24 +89,44 @@ function TestButton({ translator, api }) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return <CircularProgress size={16} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button size="small" variant="contained" onClick={handleApiTest}>
|
<LoadingButton
|
||||||
|
size="small"
|
||||||
|
variant="contained"
|
||||||
|
onClick={handleApiTest}
|
||||||
|
loading={loading}
|
||||||
|
>
|
||||||
{i18n("click_test")}
|
{i18n("click_test")}
|
||||||
</Button>
|
</LoadingButton>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ApiFields({ translator }) {
|
function ApiFields({ translator }) {
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const { api, updateApi, resetApi } = useApi(translator);
|
const { api, updateApi, resetApi } = useApi(translator);
|
||||||
const { url = "", key = "", model = "", prompt = "" } = api;
|
const {
|
||||||
|
url = "",
|
||||||
|
key = "",
|
||||||
|
model = "",
|
||||||
|
prompt = "",
|
||||||
|
fetchLimit = DEFAULT_FETCH_LIMIT,
|
||||||
|
fetchInterval = DEFAULT_FETCH_INTERVAL,
|
||||||
|
dictNo = "",
|
||||||
|
memoryNo = "",
|
||||||
|
customOption = "",
|
||||||
|
} = api;
|
||||||
|
|
||||||
const handleChange = (e) => {
|
const handleChange = (e) => {
|
||||||
const { name, value } = e.target;
|
let { name, value } = e.target;
|
||||||
|
switch (name) {
|
||||||
|
case "fetchLimit":
|
||||||
|
value = limitNumber(value, 1, 100);
|
||||||
|
break;
|
||||||
|
case "fetchInterval":
|
||||||
|
value = limitNumber(value, 0, 5000);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
}
|
||||||
updateApi({
|
updateApi({
|
||||||
[name]: value,
|
[name]: value,
|
||||||
});
|
});
|
||||||
@@ -95,6 +139,28 @@ function ApiFields({ translator }) {
|
|||||||
OPT_TRANS_TENCENT,
|
OPT_TRANS_TENCENT,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const mulkeysTranslators = [
|
||||||
|
OPT_TRANS_DEEPL,
|
||||||
|
OPT_TRANS_OPENAI,
|
||||||
|
OPT_TRANS_GEMINI,
|
||||||
|
OPT_TRANS_CLOUDFLAREAI,
|
||||||
|
OPT_TRANS_NIUTRANS,
|
||||||
|
];
|
||||||
|
|
||||||
|
const keyHelper =
|
||||||
|
translator === OPT_TRANS_NIUTRANS ? (
|
||||||
|
<>
|
||||||
|
{i18n("mulkeys_help")}
|
||||||
|
<Link href={URL_NIUTRANS_REG} target="_blank">
|
||||||
|
{i18n("reg_niutrans")}
|
||||||
|
</Link>
|
||||||
|
</>
|
||||||
|
) : mulkeysTranslators.includes(translator) ? (
|
||||||
|
i18n("mulkeys_help")
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack spacing={3}>
|
<Stack spacing={3}>
|
||||||
{!buildinTranslators.includes(translator) && (
|
{!buildinTranslators.includes(translator) && (
|
||||||
@@ -112,10 +178,13 @@ function ApiFields({ translator }) {
|
|||||||
name="key"
|
name="key"
|
||||||
value={key}
|
value={key}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
|
multiline={mulkeysTranslators.includes(translator)}
|
||||||
|
helperText={keyHelper}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{translator === OPT_TRANS_OPENAI && (
|
|
||||||
|
{(translator === OPT_TRANS_OPENAI || translator === OPT_TRANS_GEMINI) && (
|
||||||
<>
|
<>
|
||||||
<TextField
|
<TextField
|
||||||
size="small"
|
size="small"
|
||||||
@@ -135,22 +204,68 @@ function ApiFields({ translator }) {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{translator === OPT_TRANS_NIUTRANS && (
|
||||||
|
<>
|
||||||
|
<TextField
|
||||||
|
size="small"
|
||||||
|
label={"DictNo"}
|
||||||
|
name="dictNo"
|
||||||
|
value={dictNo}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
size="small"
|
||||||
|
label={"MemoryNo"}
|
||||||
|
name="memoryNo"
|
||||||
|
value={memoryNo}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{translator.startsWith(OPT_TRANS_CUSTOMIZE) && (
|
||||||
|
<TextField
|
||||||
|
size="small"
|
||||||
|
label={i18n("custom_option")}
|
||||||
|
name="customOption"
|
||||||
|
value={customOption}
|
||||||
|
onChange={handleChange}
|
||||||
|
multiline
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
size="small"
|
||||||
|
label={i18n("fetch_limit")}
|
||||||
|
type="number"
|
||||||
|
name="fetchLimit"
|
||||||
|
value={fetchLimit}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
size="small"
|
||||||
|
label={i18n("fetch_interval")}
|
||||||
|
type="number"
|
||||||
|
name="fetchInterval"
|
||||||
|
value={fetchInterval}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
|
||||||
<Stack direction="row" spacing={2}>
|
<Stack direction="row" spacing={2}>
|
||||||
<TestButton translator={translator} api={api} />
|
<TestButton translator={translator} api={api} />
|
||||||
{!buildinTranslators.includes(translator) && (
|
<Button
|
||||||
<Button
|
size="small"
|
||||||
size="small"
|
variant="outlined"
|
||||||
variant="outlined"
|
onClick={() => {
|
||||||
onClick={() => {
|
resetApi();
|
||||||
resetApi();
|
}}
|
||||||
}}
|
>
|
||||||
>
|
{i18n("restore_default")}
|
||||||
{i18n("restore_default")}
|
</Button>
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
{translator === OPT_TRANS_CUSTOMIZE && (
|
{translator.startsWith(OPT_TRANS_CUSTOMIZE) && (
|
||||||
<pre>{i18n("custom_api_help")}</pre>
|
<pre>{i18n("custom_api_help")}</pre>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
import FileDownloadIcon from "@mui/icons-material/FileDownload";
|
import FileDownloadIcon from "@mui/icons-material/FileDownload";
|
||||||
import Button from "@mui/material/Button";
|
import LoadingButton from "@mui/lab/LoadingButton";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { kissLog } from "../../libs/log";
|
||||||
|
|
||||||
export default function DownloadButton({ data, text, fileName }) {
|
export default function DownloadButton({ handleData, text, fileName }) {
|
||||||
const handleClick = (e) => {
|
const [loading, setLoading] = useState(false);
|
||||||
|
const handleClick = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (data) {
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
const data = await handleData();
|
||||||
const url = window.URL.createObjectURL(new Blob([data]));
|
const url = window.URL.createObjectURL(new Blob([data]));
|
||||||
const link = document.createElement("a");
|
const link = document.createElement("a");
|
||||||
link.href = url;
|
link.href = url;
|
||||||
@@ -12,16 +17,21 @@ export default function DownloadButton({ data, text, fileName }) {
|
|||||||
document.body.appendChild(link);
|
document.body.appendChild(link);
|
||||||
link.click();
|
link.click();
|
||||||
link.remove();
|
link.remove();
|
||||||
|
} catch (err) {
|
||||||
|
kissLog(err, "download");
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<Button
|
<LoadingButton
|
||||||
size="small"
|
size="small"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
|
loading={loading}
|
||||||
startIcon={<FileDownloadIcon />}
|
startIcon={<FileDownloadIcon />}
|
||||||
>
|
>
|
||||||
{text}
|
{text}
|
||||||
</Button>
|
</LoadingButton>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import Stack from "@mui/material/Stack";
|
import Stack from "@mui/material/Stack";
|
||||||
import { OPT_TRANS_BAIDU } from "../../config";
|
import { useState } from "react";
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
import Accordion from "@mui/material/Accordion";
|
import Accordion from "@mui/material/Accordion";
|
||||||
import AccordionSummary from "@mui/material/AccordionSummary";
|
import AccordionSummary from "@mui/material/AccordionSummary";
|
||||||
@@ -8,52 +7,18 @@ import AccordionDetails from "@mui/material/AccordionDetails";
|
|||||||
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
||||||
import CircularProgress from "@mui/material/CircularProgress";
|
import CircularProgress from "@mui/material/CircularProgress";
|
||||||
import { useI18n } from "../../hooks/I18n";
|
import { useI18n } from "../../hooks/I18n";
|
||||||
import Alert from "@mui/material/Alert";
|
|
||||||
import { apiTranslate } from "../../apis";
|
|
||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
import { useFavWords } from "../../hooks/FavWords";
|
import { useFavWords } from "../../hooks/FavWords";
|
||||||
import DictCont from "../Selection/DictCont";
|
import DictCont from "../Selection/DictCont";
|
||||||
|
import SugCont from "../Selection/SugCont";
|
||||||
import DownloadButton from "./DownloadButton";
|
import DownloadButton from "./DownloadButton";
|
||||||
import UploadButton from "./UploadButton";
|
import UploadButton from "./UploadButton";
|
||||||
import Button from "@mui/material/Button";
|
import Button from "@mui/material/Button";
|
||||||
import ClearAllIcon from "@mui/icons-material/ClearAll";
|
import ClearAllIcon from "@mui/icons-material/ClearAll";
|
||||||
import { isValidWord } from "../../libs/utils";
|
import { isValidWord } from "../../libs/utils";
|
||||||
|
import { kissLog } from "../../libs/log";
|
||||||
function DictField({ word }) {
|
import { apiTranslate } from "../../apis";
|
||||||
const [dictResult, setDictResult] = useState(null);
|
import { OPT_TRANS_BAIDU, PHONIC_MAP } from "../../config";
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [error, setError] = useState("");
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
(async () => {
|
|
||||||
try {
|
|
||||||
setLoading(true);
|
|
||||||
setError("");
|
|
||||||
const dictRes = await apiTranslate({
|
|
||||||
text: word,
|
|
||||||
translator: OPT_TRANS_BAIDU,
|
|
||||||
fromLang: "en",
|
|
||||||
toLang: "zh-CN",
|
|
||||||
});
|
|
||||||
setDictResult(dictRes[2].dict_result);
|
|
||||||
} catch (err) {
|
|
||||||
setError(err.message);
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
}, [word]);
|
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return <CircularProgress size={24} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
return <Alert severity="error">{error}</Alert>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <DictCont dictResult={dictResult} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
function FavAccordion({ word, index }) {
|
function FavAccordion({ word, index }) {
|
||||||
const [expanded, setExpanded] = useState(false);
|
const [expanded, setExpanded] = useState(false);
|
||||||
@@ -71,7 +36,12 @@ function FavAccordion({ word, index }) {
|
|||||||
<Typography>{`${index + 1}. ${word}`}</Typography>
|
<Typography>{`${index + 1}. ${word}`}</Typography>
|
||||||
</AccordionSummary>
|
</AccordionSummary>
|
||||||
<AccordionDetails>
|
<AccordionDetails>
|
||||||
{expanded && <DictField word={word} />}
|
{expanded && (
|
||||||
|
<Stack spacing={2}>
|
||||||
|
<DictCont text={word} />
|
||||||
|
<SugCont text={word} />
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
</AccordionDetails>
|
</AccordionDetails>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
);
|
);
|
||||||
@@ -93,10 +63,49 @@ export default function FavWords() {
|
|||||||
.filter(isValidWord);
|
.filter(isValidWord);
|
||||||
await mergeWords(newWords);
|
await mergeWords(newWords);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("[import rules]", err);
|
kissLog(err, "import rules");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleTranslation = async () => {
|
||||||
|
const tranList = [];
|
||||||
|
for (const text of downloadList) {
|
||||||
|
try {
|
||||||
|
const dictRes = await apiTranslate({
|
||||||
|
text,
|
||||||
|
translator: OPT_TRANS_BAIDU,
|
||||||
|
fromLang: "en",
|
||||||
|
toLang: "zh-CN",
|
||||||
|
});
|
||||||
|
if (dictRes[2]?.type === 1) {
|
||||||
|
tranList.push(JSON.parse(dictRes[2].result));
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
// skip
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tranList
|
||||||
|
.map((dictResult) =>
|
||||||
|
[
|
||||||
|
`## ${dictResult.src}`,
|
||||||
|
dictResult.voice
|
||||||
|
?.map(Object.entries)
|
||||||
|
.map((item) => item[0])
|
||||||
|
.map(([key, val]) => `${PHONIC_MAP[key]?.[0] || key} ${val}`)
|
||||||
|
.join(" "),
|
||||||
|
dictResult.content[0].mean
|
||||||
|
.map(({ pre, cont }) => {
|
||||||
|
return ` - ${pre ? `[${pre}] ` : ""}${Object.keys(cont).join(
|
||||||
|
"; "
|
||||||
|
)}`;
|
||||||
|
})
|
||||||
|
.join("\n"),
|
||||||
|
].join("\n\n")
|
||||||
|
)
|
||||||
|
.join("\n\n");
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Stack spacing={3}>
|
<Stack spacing={3}>
|
||||||
@@ -114,10 +123,15 @@ export default function FavWords() {
|
|||||||
fileExts={[".txt", ".csv"]}
|
fileExts={[".txt", ".csv"]}
|
||||||
/>
|
/>
|
||||||
<DownloadButton
|
<DownloadButton
|
||||||
data={downloadList.join("\n")}
|
handleData={() => downloadList.join("\n")}
|
||||||
text={i18n("export")}
|
text={i18n("export")}
|
||||||
fileName={`kiss-words_${Date.now()}.txt`}
|
fileName={`kiss-words_${Date.now()}.txt`}
|
||||||
/>
|
/>
|
||||||
|
<DownloadButton
|
||||||
|
handleData={handleTranslation}
|
||||||
|
text={i18n("export_translation")}
|
||||||
|
fileName={`kiss-words_${Date.now()}.md`}
|
||||||
|
/>
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import Box from "@mui/material/Box";
|
|||||||
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";
|
import DarkModeButton from "./DarkModeButton";
|
||||||
|
import Typography from "@mui/material/Typography";
|
||||||
|
|
||||||
function Header(props) {
|
function Header(props) {
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
@@ -30,14 +31,14 @@ function Header(props) {
|
|||||||
<MenuIcon />
|
<MenuIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Box>
|
</Box>
|
||||||
<Box sx={{ flexGrow: 1 }}>
|
<Typography component="div" sx={{ flexGrow: 1, fontWeight: "bold" }}>
|
||||||
<Link
|
<Link
|
||||||
underline="none"
|
underline="none"
|
||||||
color="inherit"
|
color="inherit"
|
||||||
href={process.env.REACT_APP_HOMEPAGE}
|
href={process.env.REACT_APP_HOMEPAGE}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>{`${i18n("app_name")} v${process.env.REACT_APP_VERSION}`}</Link>
|
>{`${i18n("app_name")} v${process.env.REACT_APP_VERSION}`}</Link>
|
||||||
</Box>
|
</Typography>
|
||||||
<DarkModeButton />
|
<DarkModeButton />
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
</AppBar>
|
</AppBar>
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ 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 ApiIcon from "@mui/icons-material/Api";
|
||||||
import SendTimeExtensionIcon from "@mui/icons-material/SendTimeExtension";
|
|
||||||
import InputIcon from "@mui/icons-material/Input";
|
import InputIcon from "@mui/icons-material/Input";
|
||||||
import SelectAllIcon from "@mui/icons-material/SelectAll";
|
import SelectAllIcon from "@mui/icons-material/SelectAll";
|
||||||
import EventNoteIcon from "@mui/icons-material/EventNote";
|
import EventNoteIcon from "@mui/icons-material/EventNote";
|
||||||
@@ -65,12 +64,6 @@ export default function Navigator(props) {
|
|||||||
url: "/sync",
|
url: "/sync",
|
||||||
icon: <SyncIcon />,
|
icon: <SyncIcon />,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: "webfix",
|
|
||||||
label: i18n("patch_setting"),
|
|
||||||
url: "/webfix",
|
|
||||||
icon: <SendTimeExtensionIcon />,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: "words",
|
id: "words",
|
||||||
label: i18n("favorite_words"),
|
label: i18n("favorite_words"),
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import Alert from "@mui/material/Alert";
|
|||||||
import {
|
import {
|
||||||
GLOBAL_KEY,
|
GLOBAL_KEY,
|
||||||
DEFAULT_RULE,
|
DEFAULT_RULE,
|
||||||
|
GLOBLA_RULE,
|
||||||
OPT_LANGS_FROM,
|
OPT_LANGS_FROM,
|
||||||
OPT_LANGS_TO,
|
OPT_LANGS_TO,
|
||||||
OPT_TRANS_ALL,
|
OPT_TRANS_ALL,
|
||||||
@@ -15,6 +16,9 @@ import {
|
|||||||
OPT_STYLE_USE_COLOR,
|
OPT_STYLE_USE_COLOR,
|
||||||
URL_KISS_RULES_NEW_ISSUE,
|
URL_KISS_RULES_NEW_ISSUE,
|
||||||
OPT_SYNCTYPE_WORKER,
|
OPT_SYNCTYPE_WORKER,
|
||||||
|
OPT_TIMING_PAGESCROLL,
|
||||||
|
DEFAULT_TRANS_TAG,
|
||||||
|
OPT_TIMING_ALL,
|
||||||
} from "../../config";
|
} from "../../config";
|
||||||
import { useState, useEffect, useMemo } from "react";
|
import { useState, useEffect, useMemo } from "react";
|
||||||
import { useI18n } from "../../hooks/I18n";
|
import { useI18n } from "../../hooks/I18n";
|
||||||
@@ -50,11 +54,17 @@ import HelpButton from "./HelpButton";
|
|||||||
import { useSyncCaches } from "../../hooks/Sync";
|
import { useSyncCaches } from "../../hooks/Sync";
|
||||||
import DownloadButton from "./DownloadButton";
|
import DownloadButton from "./DownloadButton";
|
||||||
import UploadButton from "./UploadButton";
|
import UploadButton from "./UploadButton";
|
||||||
|
import { FIXER_ALL } from "../../libs/webfix";
|
||||||
|
import AddIcon from "@mui/icons-material/Add";
|
||||||
|
import EditIcon from "@mui/icons-material/Edit";
|
||||||
|
import CancelIcon from "@mui/icons-material/Cancel";
|
||||||
|
import SaveIcon from "@mui/icons-material/Save";
|
||||||
|
import { kissLog } from "../../libs/log";
|
||||||
|
|
||||||
function RuleFields({ rule, rules, setShow, setKeyword }) {
|
function RuleFields({ rule, rules, setShow, setKeyword }) {
|
||||||
const initFormValues = rule || {
|
const initFormValues = {
|
||||||
...DEFAULT_RULE,
|
...(rule?.pattern === "*" ? GLOBLA_RULE : DEFAULT_RULE),
|
||||||
transOpen: "true",
|
...(rule || {}),
|
||||||
};
|
};
|
||||||
const editMode = !!rule;
|
const editMode = !!rule;
|
||||||
|
|
||||||
@@ -62,9 +72,16 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
|
|||||||
const [disabled, setDisabled] = useState(editMode);
|
const [disabled, setDisabled] = useState(editMode);
|
||||||
const [errors, setErrors] = useState({});
|
const [errors, setErrors] = useState({});
|
||||||
const [formValues, setFormValues] = useState(initFormValues);
|
const [formValues, setFormValues] = useState(initFormValues);
|
||||||
|
const [showMore, setShowMore] = useState(!rules);
|
||||||
const {
|
const {
|
||||||
pattern,
|
pattern,
|
||||||
selector,
|
selector,
|
||||||
|
keepSelector = "",
|
||||||
|
terms = "",
|
||||||
|
selectStyle = "",
|
||||||
|
parentStyle = "",
|
||||||
|
injectJs = "",
|
||||||
|
injectCss = "",
|
||||||
translator,
|
translator,
|
||||||
fromLang,
|
fromLang,
|
||||||
toLang,
|
toLang,
|
||||||
@@ -72,6 +89,14 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
|
|||||||
transOpen,
|
transOpen,
|
||||||
bgColor,
|
bgColor,
|
||||||
textDiyStyle,
|
textDiyStyle,
|
||||||
|
transOnly = "false",
|
||||||
|
transTiming = OPT_TIMING_PAGESCROLL,
|
||||||
|
transTag = DEFAULT_TRANS_TAG,
|
||||||
|
transTitle = "false",
|
||||||
|
detectRemote = "false",
|
||||||
|
skipLangs = [],
|
||||||
|
fixerSelector = "",
|
||||||
|
fixerFunc = "-",
|
||||||
} = formValues;
|
} = formValues;
|
||||||
|
|
||||||
const hasSamePattern = (str) => {
|
const hasSamePattern = (str) => {
|
||||||
@@ -179,6 +204,16 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
|
|||||||
onFocus={handleFocus}
|
onFocus={handleFocus}
|
||||||
multiline
|
multiline
|
||||||
/>
|
/>
|
||||||
|
<TextField
|
||||||
|
size="small"
|
||||||
|
label={i18n("keep_selector")}
|
||||||
|
helperText={i18n("keep_selector_helper")}
|
||||||
|
name="keepSelector"
|
||||||
|
value={keepSelector}
|
||||||
|
disabled={disabled}
|
||||||
|
onChange={handleChange}
|
||||||
|
multiline
|
||||||
|
/>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Grid container spacing={2} columns={12}>
|
<Grid container spacing={2} columns={12}>
|
||||||
@@ -299,10 +334,206 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
|
|||||||
value={textDiyStyle}
|
value={textDiyStyle}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
|
maxRows={10}
|
||||||
multiline
|
multiline
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{showMore && (
|
||||||
|
<>
|
||||||
|
<Box>
|
||||||
|
<Grid container spacing={2} columns={12}>
|
||||||
|
<Grid item xs={12} sm={6} md={3} lg={2}>
|
||||||
|
<TextField
|
||||||
|
select
|
||||||
|
size="small"
|
||||||
|
fullWidth
|
||||||
|
name="transOnly"
|
||||||
|
value={transOnly}
|
||||||
|
label={i18n("show_only_translations")}
|
||||||
|
disabled={disabled}
|
||||||
|
onChange={handleChange}
|
||||||
|
>
|
||||||
|
{GlobalItem}
|
||||||
|
<MenuItem value={"false"}>{i18n("disable")}</MenuItem>
|
||||||
|
<MenuItem value={"true"}>{i18n("enable")}</MenuItem>
|
||||||
|
</TextField>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} sm={6} md={3} lg={2}>
|
||||||
|
<TextField
|
||||||
|
select
|
||||||
|
size="small"
|
||||||
|
fullWidth
|
||||||
|
name="transTiming"
|
||||||
|
value={transTiming}
|
||||||
|
label={i18n("trigger_mode")}
|
||||||
|
disabled={disabled}
|
||||||
|
onChange={handleChange}
|
||||||
|
>
|
||||||
|
{GlobalItem}
|
||||||
|
{OPT_TIMING_ALL.map((item) => (
|
||||||
|
<MenuItem key={item} value={item}>
|
||||||
|
{i18n(item)}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</TextField>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} sm={6} md={3} lg={2}>
|
||||||
|
<TextField
|
||||||
|
select
|
||||||
|
size="small"
|
||||||
|
fullWidth
|
||||||
|
name="transTag"
|
||||||
|
value={transTag}
|
||||||
|
label={i18n("translation_element_tag")}
|
||||||
|
disabled={disabled}
|
||||||
|
onChange={handleChange}
|
||||||
|
>
|
||||||
|
{GlobalItem}
|
||||||
|
<MenuItem value={"span"}>{`<span>`}</MenuItem>
|
||||||
|
<MenuItem value={"font"}>{`<font>`}</MenuItem>
|
||||||
|
</TextField>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} sm={6} md={3} lg={2}>
|
||||||
|
<TextField
|
||||||
|
select
|
||||||
|
size="small"
|
||||||
|
fullWidth
|
||||||
|
name="transTitle"
|
||||||
|
value={transTitle}
|
||||||
|
label={i18n("translate_page_title")}
|
||||||
|
disabled={disabled}
|
||||||
|
onChange={handleChange}
|
||||||
|
>
|
||||||
|
{GlobalItem}
|
||||||
|
<MenuItem value={"false"}>{i18n("disable")}</MenuItem>
|
||||||
|
<MenuItem value={"true"}>{i18n("enable")}</MenuItem>
|
||||||
|
</TextField>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} sm={6} md={3} lg={2}>
|
||||||
|
<TextField
|
||||||
|
select
|
||||||
|
size="small"
|
||||||
|
fullWidth
|
||||||
|
name="detectRemote"
|
||||||
|
value={detectRemote}
|
||||||
|
label={i18n("detect_lang_remote")}
|
||||||
|
disabled={disabled}
|
||||||
|
onChange={handleChange}
|
||||||
|
>
|
||||||
|
{GlobalItem}
|
||||||
|
<MenuItem value={"false"}>{i18n("disable")}</MenuItem>
|
||||||
|
<MenuItem value={"true"}>{i18n("enable")}</MenuItem>
|
||||||
|
</TextField>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
select
|
||||||
|
size="small"
|
||||||
|
label={i18n("skip_langs")}
|
||||||
|
helperText={i18n("skip_langs_helper")}
|
||||||
|
name="skipLangs"
|
||||||
|
value={skipLangs}
|
||||||
|
disabled={disabled}
|
||||||
|
onChange={handleChange}
|
||||||
|
SelectProps={{
|
||||||
|
multiple: true,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{OPT_LANGS_TO.map(([langKey, langName]) => (
|
||||||
|
<MenuItem key={langKey} value={langKey}>
|
||||||
|
{langName}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</TextField>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
size="small"
|
||||||
|
label={i18n("terms")}
|
||||||
|
helperText={i18n("terms_helper")}
|
||||||
|
name="terms"
|
||||||
|
value={terms}
|
||||||
|
disabled={disabled}
|
||||||
|
onChange={handleChange}
|
||||||
|
multiline
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
size="small"
|
||||||
|
label={i18n("fixer_selector")}
|
||||||
|
name="fixerSelector"
|
||||||
|
value={fixerSelector}
|
||||||
|
disabled={disabled}
|
||||||
|
onChange={handleChange}
|
||||||
|
multiline
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
select
|
||||||
|
size="small"
|
||||||
|
name="fixerFunc"
|
||||||
|
value={fixerFunc}
|
||||||
|
label={i18n("fixer_function")}
|
||||||
|
helperText={i18n("fixer_function_helper")}
|
||||||
|
disabled={disabled}
|
||||||
|
onChange={handleChange}
|
||||||
|
>
|
||||||
|
{GlobalItem}
|
||||||
|
{FIXER_ALL.map((item) => (
|
||||||
|
<MenuItem key={item} value={item}>
|
||||||
|
{item}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</TextField>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
size="small"
|
||||||
|
label={i18n("selector_style")}
|
||||||
|
helperText={i18n("selector_style_helper")}
|
||||||
|
name="selectStyle"
|
||||||
|
value={selectStyle}
|
||||||
|
disabled={disabled}
|
||||||
|
onChange={handleChange}
|
||||||
|
maxRows={10}
|
||||||
|
multiline
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
size="small"
|
||||||
|
label={i18n("selector_parent_style")}
|
||||||
|
helperText={i18n("selector_style_helper")}
|
||||||
|
name="parentStyle"
|
||||||
|
value={parentStyle}
|
||||||
|
disabled={disabled}
|
||||||
|
onChange={handleChange}
|
||||||
|
maxRows={10}
|
||||||
|
multiline
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
size="small"
|
||||||
|
label={i18n("inject_css")}
|
||||||
|
helperText={i18n("inject_css_helper")}
|
||||||
|
name="injectCss"
|
||||||
|
value={injectCss}
|
||||||
|
disabled={disabled}
|
||||||
|
onChange={handleChange}
|
||||||
|
maxRows={10}
|
||||||
|
multiline
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
size="small"
|
||||||
|
label={i18n("inject_js")}
|
||||||
|
helperText={i18n("inject_js_helper")}
|
||||||
|
name="injectJs"
|
||||||
|
value={injectJs}
|
||||||
|
disabled={disabled}
|
||||||
|
onChange={handleChange}
|
||||||
|
maxRows={10}
|
||||||
|
multiline
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
{rules &&
|
{rules &&
|
||||||
(editMode ? (
|
(editMode ? (
|
||||||
// 编辑
|
// 编辑
|
||||||
@@ -316,6 +547,7 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setDisabled(false);
|
setDisabled(false);
|
||||||
}}
|
}}
|
||||||
|
startIcon={<EditIcon />}
|
||||||
>
|
>
|
||||||
{i18n("edit")}
|
{i18n("edit")}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -327,35 +559,87 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
rules.del(rule.pattern);
|
rules.del(rule.pattern);
|
||||||
}}
|
}}
|
||||||
|
startIcon={<DeleteIcon />}
|
||||||
>
|
>
|
||||||
{i18n("delete")}
|
{i18n("delete")}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
{!showMore && (
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
variant="text"
|
||||||
|
onClick={() => {
|
||||||
|
setShowMore(true);
|
||||||
|
}}
|
||||||
|
startIcon={<ExpandMoreIcon />}
|
||||||
|
>
|
||||||
|
{i18n("more")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Button size="small" variant="contained" type="submit">
|
<Button
|
||||||
|
size="small"
|
||||||
|
variant="contained"
|
||||||
|
type="submit"
|
||||||
|
startIcon={<SaveIcon />}
|
||||||
|
>
|
||||||
{i18n("save")}
|
{i18n("save")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
onClick={handleCancel}
|
onClick={handleCancel}
|
||||||
|
startIcon={<CancelIcon />}
|
||||||
>
|
>
|
||||||
{i18n("cancel")}
|
{i18n("cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
|
{!showMore && (
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
variant="text"
|
||||||
|
onClick={() => {
|
||||||
|
setShowMore(true);
|
||||||
|
}}
|
||||||
|
startIcon={<ExpandMoreIcon />}
|
||||||
|
>
|
||||||
|
{i18n("more")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
) : (
|
) : (
|
||||||
// 添加
|
// 添加
|
||||||
<Stack direction="row" spacing={2}>
|
<Stack direction="row" spacing={2}>
|
||||||
<Button size="small" variant="contained" type="submit">
|
<Button
|
||||||
|
size="small"
|
||||||
|
variant="contained"
|
||||||
|
type="submit"
|
||||||
|
startIcon={<SaveIcon />}
|
||||||
|
>
|
||||||
{i18n("save")}
|
{i18n("save")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button size="small" variant="outlined" onClick={handleCancel}>
|
<Button
|
||||||
|
size="small"
|
||||||
|
variant="outlined"
|
||||||
|
onClick={handleCancel}
|
||||||
|
startIcon={<CancelIcon />}
|
||||||
|
>
|
||||||
{i18n("cancel")}
|
{i18n("cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
|
{!showMore && (
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
variant="text"
|
||||||
|
onClick={() => {
|
||||||
|
setShowMore(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{i18n("more")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
))}
|
))}
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -418,7 +702,7 @@ function ShareButton({ rules, injectRules, selectedUrl }) {
|
|||||||
window.open(url, "_blank");
|
window.open(url, "_blank");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
alert.warning(i18n("error_got_some_wrong"));
|
alert.warning(i18n("error_got_some_wrong"));
|
||||||
console.log("[share rules]", err);
|
kissLog(err, "share rules");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -448,7 +732,7 @@ function UserRules({ subRules }) {
|
|||||||
try {
|
try {
|
||||||
await rules.merge(JSON.parse(data));
|
await rules.merge(JSON.parse(data));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("[import rules]", err);
|
kissLog(err, "import rules");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -485,13 +769,14 @@ function UserRules({ subRules }) {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setShowAdd(true);
|
setShowAdd(true);
|
||||||
}}
|
}}
|
||||||
|
startIcon={<AddIcon />}
|
||||||
>
|
>
|
||||||
{i18n("add")}
|
{i18n("add")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<UploadButton text={i18n("import")} handleImport={handleImport} />
|
<UploadButton text={i18n("import")} handleImport={handleImport} />
|
||||||
<DownloadButton
|
<DownloadButton
|
||||||
data={JSON.stringify([...rules.list].reverse(), null, 2)}
|
handleData={() => JSON.stringify([...rules.list].reverse(), null, 2)}
|
||||||
text={i18n("export")}
|
text={i18n("export")}
|
||||||
fileName={`kiss-rules_${Date.now()}.json`}
|
fileName={`kiss-rules_${Date.now()}.json`}
|
||||||
/>
|
/>
|
||||||
@@ -580,7 +865,7 @@ function SubRulesItem({
|
|||||||
await delSubRules(url);
|
await delSubRules(url);
|
||||||
await deleteDataCache(url);
|
await deleteDataCache(url);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("[del subrules]", err);
|
kissLog(err, "del subrules");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -593,7 +878,7 @@ function SubRulesItem({
|
|||||||
}
|
}
|
||||||
await updateDataCache(url);
|
await updateDataCache(url);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("[sync sub rules]", err);
|
kissLog(err, "sync sub rules");
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -672,7 +957,7 @@ function SubRulesEdit({ subList, addSub, updateDataCache }) {
|
|||||||
setShowInput(false);
|
setShowInput(false);
|
||||||
setInputText("");
|
setInputText("");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("[fetch rules]", err);
|
kissLog(err, "fetch rules");
|
||||||
setInputError(i18n("error_fetch_url"));
|
setInputError(i18n("error_fetch_url"));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -700,6 +985,7 @@ function SubRulesEdit({ subList, addSub, updateDataCache }) {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setShowInput(true);
|
setShowInput(true);
|
||||||
}}
|
}}
|
||||||
|
startIcon={<AddIcon />}
|
||||||
>
|
>
|
||||||
{i18n("add")}
|
{i18n("add")}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -724,10 +1010,16 @@ function SubRulesEdit({ subList, addSub, updateDataCache }) {
|
|||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
|
startIcon={<SaveIcon />}
|
||||||
>
|
>
|
||||||
{i18n("save")}
|
{i18n("save")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button size="small" variant="outlined" onClick={handleCancel}>
|
<Button
|
||||||
|
size="small"
|
||||||
|
variant="outlined"
|
||||||
|
onClick={handleCancel}
|
||||||
|
startIcon={<CancelIcon />}
|
||||||
|
>
|
||||||
{i18n("cancel")}
|
{i18n("cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -815,6 +1107,8 @@ export default function Rules() {
|
|||||||
{i18n("rules_warn_1")}
|
{i18n("rules_warn_1")}
|
||||||
<br />
|
<br />
|
||||||
{i18n("rules_warn_2")}
|
{i18n("rules_warn_2")}
|
||||||
|
<br />
|
||||||
|
{i18n("rules_warn_3")}
|
||||||
</Alert>
|
</Alert>
|
||||||
|
|
||||||
<Box sx={{ borderBottom: 1, borderColor: "divider" }}>
|
<Box sx={{ borderBottom: 1, borderColor: "divider" }}>
|
||||||
|
|||||||
@@ -17,16 +17,20 @@ import {
|
|||||||
UI_LANGS,
|
UI_LANGS,
|
||||||
TRANS_NEWLINE_LENGTH,
|
TRANS_NEWLINE_LENGTH,
|
||||||
CACHE_NAME,
|
CACHE_NAME,
|
||||||
OPT_MOUSEKEY_ALL,
|
|
||||||
OPT_MOUSEKEY_DISABLE,
|
|
||||||
OPT_SHORTCUT_TRANSLATE,
|
OPT_SHORTCUT_TRANSLATE,
|
||||||
OPT_SHORTCUT_STYLE,
|
OPT_SHORTCUT_STYLE,
|
||||||
OPT_SHORTCUT_POPUP,
|
OPT_SHORTCUT_POPUP,
|
||||||
OPT_SHORTCUT_SETTING,
|
OPT_SHORTCUT_SETTING,
|
||||||
|
DEFAULT_BLACKLIST,
|
||||||
|
DEFAULT_CSPLIST,
|
||||||
|
MSG_CONTEXT_MENUS,
|
||||||
|
MSG_UPDATE_CSP,
|
||||||
} from "../../config";
|
} from "../../config";
|
||||||
import { useShortcut } from "../../hooks/Shortcut";
|
import { useShortcut } from "../../hooks/Shortcut";
|
||||||
import ShortcutInput from "./ShortcutInput";
|
import ShortcutInput from "./ShortcutInput";
|
||||||
import { useFab } from "../../hooks/Fab";
|
import { useFab } from "../../hooks/Fab";
|
||||||
|
import { sendBgMsg } from "../../libs/msg";
|
||||||
|
import { kissLog } from "../../libs/log";
|
||||||
|
|
||||||
function ShortcutItem({ action, label }) {
|
function ShortcutItem({ action, label }) {
|
||||||
const { shortcut, setShortcut } = useShortcut(action);
|
const { shortcut, setShortcut } = useShortcut(action);
|
||||||
@@ -51,6 +55,9 @@ export default function Settings() {
|
|||||||
case "fetchInterval":
|
case "fetchInterval":
|
||||||
value = limitNumber(value, 0, 5000);
|
value = limitNumber(value, 0, 5000);
|
||||||
break;
|
break;
|
||||||
|
case "transInterval":
|
||||||
|
value = limitNumber(value, 100, 5000);
|
||||||
|
break;
|
||||||
case "minLength":
|
case "minLength":
|
||||||
value = limitNumber(value, 1, 100);
|
value = limitNumber(value, 1, 100);
|
||||||
break;
|
break;
|
||||||
@@ -61,7 +68,13 @@ export default function Settings() {
|
|||||||
value = limitNumber(value, 1, 1000);
|
value = limitNumber(value, 1, 1000);
|
||||||
break;
|
break;
|
||||||
case "touchTranslate":
|
case "touchTranslate":
|
||||||
value = limitNumber(value, 0, 3);
|
value = limitNumber(value, 0, 4);
|
||||||
|
break;
|
||||||
|
case "contextMenuType":
|
||||||
|
isExt && sendBgMsg(MSG_CONTEXT_MENUS, value);
|
||||||
|
break;
|
||||||
|
case "csplist":
|
||||||
|
isExt && sendBgMsg(MSG_UPDATE_CSP, value);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
@@ -75,21 +88,21 @@ export default function Settings() {
|
|||||||
caches.delete(CACHE_NAME);
|
caches.delete(CACHE_NAME);
|
||||||
alert.success(i18n("clear_success"));
|
alert.success(i18n("clear_success"));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("[clear cache]", err);
|
kissLog(err, "clear cache");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const {
|
const {
|
||||||
uiLang,
|
uiLang,
|
||||||
fetchLimit,
|
|
||||||
fetchInterval,
|
|
||||||
minLength,
|
minLength,
|
||||||
maxLength,
|
maxLength,
|
||||||
clearCache,
|
clearCache,
|
||||||
newlineLength = TRANS_NEWLINE_LENGTH,
|
newlineLength = TRANS_NEWLINE_LENGTH,
|
||||||
mouseKey = OPT_MOUSEKEY_DISABLE,
|
contextMenuType = 1,
|
||||||
detectRemote = false,
|
|
||||||
touchTranslate = 2,
|
touchTranslate = 2,
|
||||||
|
blacklist = DEFAULT_BLACKLIST.join(",\n"),
|
||||||
|
csplist = DEFAULT_CSPLIST.join(",\n"),
|
||||||
|
transInterval = 500,
|
||||||
} = setting;
|
} = setting;
|
||||||
const { isHide = false } = fab || {};
|
const { isHide = false } = fab || {};
|
||||||
|
|
||||||
@@ -112,24 +125,6 @@ export default function Settings() {
|
|||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<TextField
|
|
||||||
size="small"
|
|
||||||
label={i18n("fetch_limit")}
|
|
||||||
type="number"
|
|
||||||
name="fetchLimit"
|
|
||||||
defaultValue={fetchLimit}
|
|
||||||
onChange={handleChange}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<TextField
|
|
||||||
size="small"
|
|
||||||
label={i18n("fetch_interval")}
|
|
||||||
type="number"
|
|
||||||
name="fetchInterval"
|
|
||||||
defaultValue={fetchInterval}
|
|
||||||
onChange={handleChange}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
size="small"
|
size="small"
|
||||||
label={i18n("min_translate_length")}
|
label={i18n("min_translate_length")}
|
||||||
@@ -157,21 +152,14 @@ export default function Settings() {
|
|||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormControl size="small">
|
<TextField
|
||||||
<InputLabel>{i18n("mouseover_translation")}</InputLabel>
|
size="small"
|
||||||
<Select
|
label={i18n("translate_interval")}
|
||||||
name="mouseKey"
|
type="number"
|
||||||
value={mouseKey}
|
name="transInterval"
|
||||||
label={i18n("mouseover_translation")}
|
defaultValue={transInterval}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
>
|
/>
|
||||||
{OPT_MOUSEKEY_ALL.map((item) => (
|
|
||||||
<MenuItem key={item} value={item}>
|
|
||||||
{i18n(item)}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<FormControl size="small">
|
<FormControl size="small">
|
||||||
<InputLabel>{i18n("touch_translate_shortcut")}</InputLabel>
|
<InputLabel>{i18n("touch_translate_shortcut")}</InputLabel>
|
||||||
@@ -181,7 +169,7 @@ export default function Settings() {
|
|||||||
label={i18n("touch_translate_shortcut")}
|
label={i18n("touch_translate_shortcut")}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
>
|
>
|
||||||
{[0, 2, 3].map((item) => (
|
{[0, 2, 3, 4].map((item) => (
|
||||||
<MenuItem key={item} value={item}>
|
<MenuItem key={item} value={item}>
|
||||||
{i18n(`touch_tap_${item}`)}
|
{i18n(`touch_tap_${item}`)}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
@@ -205,37 +193,51 @@ export default function Settings() {
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormControl size="small">
|
<FormControl size="small">
|
||||||
<InputLabel>{i18n("detect_lang_remote")}</InputLabel>
|
<InputLabel>{i18n("context_menus")}</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
name="detectRemote"
|
name="contextMenuType"
|
||||||
value={detectRemote}
|
value={contextMenuType}
|
||||||
label={i18n("detect_lang_remote")}
|
label={i18n("context_menus")}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
>
|
>
|
||||||
<MenuItem value={false}>{i18n("disable")}</MenuItem>
|
<MenuItem value={0}>{i18n("hide_context_menus")}</MenuItem>
|
||||||
<MenuItem value={true}>{i18n("enable")}</MenuItem>
|
<MenuItem value={1}>{i18n("simple_context_menus")}</MenuItem>
|
||||||
|
<MenuItem value={2}>{i18n("secondary_context_menus")}</MenuItem>
|
||||||
</Select>
|
</Select>
|
||||||
<FormHelperText>{i18n("detect_lang_remote_help")}</FormHelperText>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
{isExt ? (
|
{isExt ? (
|
||||||
<FormControl size="small">
|
<>
|
||||||
<InputLabel>{i18n("if_clear_cache")}</InputLabel>
|
<FormControl size="small">
|
||||||
<Select
|
<InputLabel>{i18n("if_clear_cache")}</InputLabel>
|
||||||
name="clearCache"
|
<Select
|
||||||
value={clearCache}
|
name="clearCache"
|
||||||
label={i18n("if_clear_cache")}
|
value={clearCache}
|
||||||
|
label={i18n("if_clear_cache")}
|
||||||
|
onChange={handleChange}
|
||||||
|
>
|
||||||
|
<MenuItem value={false}>{i18n("clear_cache_never")}</MenuItem>
|
||||||
|
<MenuItem value={true}>{i18n("clear_cache_restart")}</MenuItem>
|
||||||
|
</Select>
|
||||||
|
<FormHelperText>
|
||||||
|
<Link component="button" onClick={handleClearCache}>
|
||||||
|
{i18n("clear_all_cache_now")}
|
||||||
|
</Link>
|
||||||
|
</FormHelperText>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
size="small"
|
||||||
|
label={i18n("disabled_csplist")}
|
||||||
|
helperText={
|
||||||
|
i18n("pattern_helper") + " " + i18n("disabled_csplist_helper")
|
||||||
|
}
|
||||||
|
name="csplist"
|
||||||
|
defaultValue={csplist}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
>
|
multiline
|
||||||
<MenuItem value={false}>{i18n("clear_cache_never")}</MenuItem>
|
/>
|
||||||
<MenuItem value={true}>{i18n("clear_cache_restart")}</MenuItem>
|
</>
|
||||||
</Select>
|
|
||||||
<FormHelperText>
|
|
||||||
<Link component="button" onClick={handleClearCache}>
|
|
||||||
{i18n("clear_all_cache_now")}
|
|
||||||
</Link>
|
|
||||||
</FormHelperText>
|
|
||||||
</FormControl>
|
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Box>
|
<Box>
|
||||||
@@ -268,6 +270,17 @@ export default function Settings() {
|
|||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
size="small"
|
||||||
|
label={i18n("translate_blacklist")}
|
||||||
|
helperText={i18n("pattern_helper")}
|
||||||
|
name="blacklist"
|
||||||
|
defaultValue={blacklist}
|
||||||
|
onChange={handleChange}
|
||||||
|
maxRows={10}
|
||||||
|
multiline
|
||||||
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { useSync } from "../../hooks/Sync";
|
|||||||
import Alert from "@mui/material/Alert";
|
import Alert from "@mui/material/Alert";
|
||||||
import Link from "@mui/material/Link";
|
import Link from "@mui/material/Link";
|
||||||
import MenuItem from "@mui/material/MenuItem";
|
import MenuItem from "@mui/material/MenuItem";
|
||||||
|
import LoadingButton from "@mui/lab/LoadingButton";
|
||||||
import {
|
import {
|
||||||
URL_KISS_WORKER,
|
URL_KISS_WORKER,
|
||||||
OPT_SYNCTYPE_ALL,
|
OPT_SYNCTYPE_ALL,
|
||||||
@@ -14,11 +15,10 @@ import {
|
|||||||
} from "../../config";
|
} from "../../config";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { syncSettingAndRules } from "../../libs/sync";
|
import { syncSettingAndRules } from "../../libs/sync";
|
||||||
import Button from "@mui/material/Button";
|
|
||||||
import { useAlert } from "../../hooks/Alert";
|
import { useAlert } from "../../hooks/Alert";
|
||||||
import SyncIcon from "@mui/icons-material/Sync";
|
import SyncIcon from "@mui/icons-material/Sync";
|
||||||
import CircularProgress from "@mui/material/CircularProgress";
|
|
||||||
import { useSetting } from "../../hooks/Setting";
|
import { useSetting } from "../../hooks/Setting";
|
||||||
|
import { kissLog } from "../../libs/log";
|
||||||
|
|
||||||
export default function SyncSetting() {
|
export default function SyncSetting() {
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
@@ -43,7 +43,7 @@ export default function SyncSetting() {
|
|||||||
await reloadSetting();
|
await reloadSetting();
|
||||||
alert.success(i18n("sync_success"));
|
alert.success(i18n("sync_success"));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("[sync all]", err);
|
kissLog(err, "sync all");
|
||||||
alert.error(i18n("sync_failed"));
|
alert.error(i18n("sync_failed"));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -122,16 +122,16 @@ export default function SyncSetting() {
|
|||||||
useFlexGap
|
useFlexGap
|
||||||
flexWrap="wrap"
|
flexWrap="wrap"
|
||||||
>
|
>
|
||||||
<Button
|
<LoadingButton
|
||||||
size="small"
|
size="small"
|
||||||
variant="contained"
|
variant="contained"
|
||||||
disabled={!syncUrl || !syncKey || loading}
|
disabled={!syncUrl || !syncKey || loading}
|
||||||
onClick={handleSyncTest}
|
onClick={handleSyncTest}
|
||||||
startIcon={<SyncIcon />}
|
startIcon={<SyncIcon />}
|
||||||
|
loading={loading}
|
||||||
>
|
>
|
||||||
{i18n("sync_now")}
|
{i18n("sync_now")}
|
||||||
</Button>
|
</LoadingButton>
|
||||||
{loading && <CircularProgress size={16} />}
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -3,13 +3,20 @@ import Stack from "@mui/material/Stack";
|
|||||||
import TextField from "@mui/material/TextField";
|
import TextField from "@mui/material/TextField";
|
||||||
import MenuItem from "@mui/material/MenuItem";
|
import MenuItem from "@mui/material/MenuItem";
|
||||||
import { useI18n } from "../../hooks/I18n";
|
import { useI18n } from "../../hooks/I18n";
|
||||||
import { OPT_TRANS_ALL, OPT_LANGS_FROM, OPT_LANGS_TO } from "../../config";
|
import {
|
||||||
|
OPT_TRANS_ALL,
|
||||||
|
OPT_LANGS_FROM,
|
||||||
|
OPT_LANGS_TO,
|
||||||
|
OPT_TRANBOX_TRIGGER_CLICK,
|
||||||
|
OPT_TRANBOX_TRIGGER_ALL,
|
||||||
|
} from "../../config";
|
||||||
import ShortcutInput from "./ShortcutInput";
|
import ShortcutInput from "./ShortcutInput";
|
||||||
import FormControlLabel from "@mui/material/FormControlLabel";
|
import FormControlLabel from "@mui/material/FormControlLabel";
|
||||||
import Switch from "@mui/material/Switch";
|
import Switch from "@mui/material/Switch";
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
import { limitNumber } from "../../libs/utils";
|
import { limitNumber } from "../../libs/utils";
|
||||||
import { useTranbox } from "../../hooks/Tranbox";
|
import { useTranbox } from "../../hooks/Tranbox";
|
||||||
|
import { isExt } from "../../libs/client";
|
||||||
|
|
||||||
export default function Tranbox() {
|
export default function Tranbox() {
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
@@ -44,9 +51,15 @@ export default function Tranbox() {
|
|||||||
translator,
|
translator,
|
||||||
fromLang,
|
fromLang,
|
||||||
toLang,
|
toLang,
|
||||||
|
toLang2 = "en",
|
||||||
tranboxShortcut,
|
tranboxShortcut,
|
||||||
btnOffsetX,
|
btnOffsetX,
|
||||||
btnOffsetY,
|
btnOffsetY,
|
||||||
|
hideTranBtn = false,
|
||||||
|
hideClickAway = false,
|
||||||
|
simpleStyle = false,
|
||||||
|
triggerMode = OPT_TRANBOX_TRIGGER_CLICK,
|
||||||
|
extStyles = "",
|
||||||
} = tranboxSetting;
|
} = tranboxSetting;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -111,6 +124,22 @@ export default function Tranbox() {
|
|||||||
))}
|
))}
|
||||||
</TextField>
|
</TextField>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
select
|
||||||
|
size="small"
|
||||||
|
name="toLang2"
|
||||||
|
value={toLang2}
|
||||||
|
label={i18n("to_lang2")}
|
||||||
|
helperText={i18n("to_lang2_helper")}
|
||||||
|
onChange={handleChange}
|
||||||
|
>
|
||||||
|
{[["none", "None"], ...OPT_LANGS_TO].map(([lang, name]) => (
|
||||||
|
<MenuItem key={lang} value={lang}>
|
||||||
|
{name}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</TextField>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
size="small"
|
size="small"
|
||||||
label={i18n("tranbtn_offset_x")}
|
label={i18n("tranbtn_offset_x")}
|
||||||
@@ -129,11 +158,74 @@ export default function Tranbox() {
|
|||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ShortcutInput
|
<TextField
|
||||||
value={tranboxShortcut}
|
select
|
||||||
onChange={handleShortcutInput}
|
size="small"
|
||||||
label={i18n("trigger_tranbox_shortcut")}
|
name="hideTranBtn"
|
||||||
|
value={hideTranBtn}
|
||||||
|
label={i18n("hide_tran_button")}
|
||||||
|
onChange={handleChange}
|
||||||
|
>
|
||||||
|
<MenuItem value={false}>{i18n("show")}</MenuItem>
|
||||||
|
<MenuItem value={true}>{i18n("hide")}</MenuItem>
|
||||||
|
</TextField>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
select
|
||||||
|
size="small"
|
||||||
|
name="hideClickAway"
|
||||||
|
value={hideClickAway}
|
||||||
|
label={i18n("hide_click_away")}
|
||||||
|
onChange={handleChange}
|
||||||
|
>
|
||||||
|
<MenuItem value={false}>{i18n("disable")}</MenuItem>
|
||||||
|
<MenuItem value={true}>{i18n("enable")}</MenuItem>
|
||||||
|
</TextField>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
select
|
||||||
|
size="small"
|
||||||
|
name="simpleStyle"
|
||||||
|
value={simpleStyle}
|
||||||
|
label={i18n("use_simple_style")}
|
||||||
|
onChange={handleChange}
|
||||||
|
>
|
||||||
|
<MenuItem value={false}>{i18n("disable")}</MenuItem>
|
||||||
|
<MenuItem value={true}>{i18n("enable")}</MenuItem>
|
||||||
|
</TextField>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
select
|
||||||
|
size="small"
|
||||||
|
name="triggerMode"
|
||||||
|
value={triggerMode}
|
||||||
|
label={i18n("trigger_mode")}
|
||||||
|
onChange={handleChange}
|
||||||
|
>
|
||||||
|
{OPT_TRANBOX_TRIGGER_ALL.map((item) => (
|
||||||
|
<MenuItem key={item} value={item}>
|
||||||
|
{i18n(`trigger_${item}`)}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</TextField>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
size="small"
|
||||||
|
label={i18n("extend_styles")}
|
||||||
|
name="extStyles"
|
||||||
|
defaultValue={extStyles}
|
||||||
|
onChange={handleChange}
|
||||||
|
maxRows={10}
|
||||||
|
multiline
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{!isExt && (
|
||||||
|
<ShortcutInput
|
||||||
|
value={tranboxShortcut}
|
||||||
|
onChange={handleShortcutInput}
|
||||||
|
label={i18n("trigger_tranbox_shortcut")}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,357 +0,0 @@
|
|||||||
import Stack from "@mui/material/Stack";
|
|
||||||
import TextField from "@mui/material/TextField";
|
|
||||||
import { useCallback, 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, FIXER_ALL } 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";
|
|
||||||
import MenuItem from "@mui/material/MenuItem";
|
|
||||||
import { useWebfixRules } from "../../hooks/WebfixRules";
|
|
||||||
|
|
||||||
function WebfixFields({ rule, webfix, setShow }) {
|
|
||||||
const editMode = !!rule;
|
|
||||||
const initFormValues = rule || {
|
|
||||||
pattern: "",
|
|
||||||
selector: "",
|
|
||||||
rootSelector: "",
|
|
||||||
fixer: FIXER_ALL[0],
|
|
||||||
};
|
|
||||||
const i18n = useI18n();
|
|
||||||
const [disabled, setDisabled] = useState(editMode);
|
|
||||||
const [errors, setErrors] = useState({});
|
|
||||||
const [formValues, setFormValues] = useState(initFormValues);
|
|
||||||
|
|
||||||
const { pattern, selector, rootSelector, fixer } = formValues;
|
|
||||||
|
|
||||||
const hasSamePattern = (str) => {
|
|
||||||
for (const item of webfix.list || []) {
|
|
||||||
if (item.pattern === str && rule?.pattern !== str) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleFocus = (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
const { name } = e.target;
|
|
||||||
setErrors((pre) => ({ ...pre, [name]: "" }));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleChange = (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
const { name, value } = e.target;
|
|
||||||
setFormValues((pre) => ({ ...pre, [name]: value }));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCancel = (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
if (editMode) {
|
|
||||||
setDisabled(true);
|
|
||||||
} else {
|
|
||||||
setShow(false);
|
|
||||||
}
|
|
||||||
setFormValues(initFormValues);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
const errors = {};
|
|
||||||
if (!pattern.trim()) {
|
|
||||||
errors.pattern = i18n("error_cant_be_blank");
|
|
||||||
}
|
|
||||||
if (hasSamePattern(pattern)) {
|
|
||||||
errors.pattern = i18n("error_duplicate_values");
|
|
||||||
}
|
|
||||||
if (!selector.trim()) {
|
|
||||||
errors.selector = i18n("error_cant_be_blank");
|
|
||||||
}
|
|
||||||
if (Object.keys(errors).length > 0) {
|
|
||||||
setErrors(errors);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (editMode) {
|
|
||||||
// 编辑
|
|
||||||
setDisabled(true);
|
|
||||||
webfix.put(rule.pattern, formValues);
|
|
||||||
} else {
|
|
||||||
// 添加
|
|
||||||
webfix.add(formValues);
|
|
||||||
setShow(false);
|
|
||||||
setFormValues(initFormValues);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<form onSubmit={handleSubmit}>
|
|
||||||
<Stack spacing={3}>
|
|
||||||
<TextField
|
|
||||||
size="small"
|
|
||||||
label={i18n("pattern")}
|
|
||||||
error={!!errors.pattern}
|
|
||||||
helperText={errors.pattern}
|
|
||||||
name="pattern"
|
|
||||||
value={pattern}
|
|
||||||
disabled={disabled}
|
|
||||||
onChange={handleChange}
|
|
||||||
onFocus={handleFocus}
|
|
||||||
multiline
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
size="small"
|
|
||||||
label={i18n("root_selector")}
|
|
||||||
error={!!errors.rootSelector}
|
|
||||||
helperText={errors.rootSelector}
|
|
||||||
name="rootSelector"
|
|
||||||
value={rootSelector}
|
|
||||||
disabled={disabled}
|
|
||||||
onChange={handleChange}
|
|
||||||
onFocus={handleFocus}
|
|
||||||
multiline
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
size="small"
|
|
||||||
label={i18n("selector")}
|
|
||||||
error={!!errors.selector}
|
|
||||||
helperText={errors.selector}
|
|
||||||
name="selector"
|
|
||||||
value={selector}
|
|
||||||
disabled={disabled}
|
|
||||||
onChange={handleChange}
|
|
||||||
onFocus={handleFocus}
|
|
||||||
multiline
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
select
|
|
||||||
size="small"
|
|
||||||
name="fixer"
|
|
||||||
value={fixer}
|
|
||||||
label={i18n("fixer_function")}
|
|
||||||
disabled={disabled}
|
|
||||||
onChange={handleChange}
|
|
||||||
>
|
|
||||||
{FIXER_ALL.map((item) => (
|
|
||||||
<MenuItem key={item} value={item}>
|
|
||||||
{item}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</TextField>
|
|
||||||
|
|
||||||
{webfix &&
|
|
||||||
(editMode ? (
|
|
||||||
// 编辑
|
|
||||||
<Stack direction="row" spacing={2}>
|
|
||||||
{disabled ? (
|
|
||||||
<>
|
|
||||||
<Button
|
|
||||||
size="small"
|
|
||||||
variant="contained"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
setDisabled(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{i18n("edit")}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
size="small"
|
|
||||||
variant="outlined"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
webfix.del(rule.pattern);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{i18n("delete")}
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Button size="small" variant="contained" type="submit">
|
|
||||||
{i18n("save")}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
size="small"
|
|
||||||
variant="outlined"
|
|
||||||
onClick={handleCancel}
|
|
||||||
>
|
|
||||||
{i18n("cancel")}
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
) : (
|
|
||||||
// 添加
|
|
||||||
<Stack direction="row" spacing={2}>
|
|
||||||
<Button size="small" variant="contained" type="submit">
|
|
||||||
{i18n("save")}
|
|
||||||
</Button>
|
|
||||||
<Button size="small" variant="outlined" onClick={handleCancel}>
|
|
||||||
{i18n("cancel")}
|
|
||||||
</Button>
|
|
||||||
</Stack>
|
|
||||||
))}
|
|
||||||
</Stack>
|
|
||||||
</form>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function WebfixAccordion({ rule, webfix }) {
|
|
||||||
const [expanded, setExpanded] = useState(false);
|
|
||||||
|
|
||||||
const handleChange = (e) => {
|
|
||||||
setExpanded((pre) => !pre);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Accordion expanded={expanded} onChange={handleChange}>
|
|
||||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
opacity: webfix ? 1 : 0.5,
|
|
||||||
overflowWrap: "anywhere",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{rule.pattern}
|
|
||||||
</Typography>
|
|
||||||
</AccordionSummary>
|
|
||||||
<AccordionDetails>
|
|
||||||
{expanded && <WebfixFields rule={rule} webfix={webfix} />}
|
|
||||||
</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 [showAdd, setShowAdd] = useState(false);
|
|
||||||
const webfix = useWebfixRules();
|
|
||||||
|
|
||||||
const loadSites = useCallback(async () => {
|
|
||||||
const sites = await loadOrFetchWebfix(process.env.REACT_APP_WEBFIXURL);
|
|
||||||
setSites(sites);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleSyncTest = async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
try {
|
|
||||||
setLoading(true);
|
|
||||||
await syncWebfix(process.env.REACT_APP_WEBFIXURL);
|
|
||||||
await loadSites();
|
|
||||||
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);
|
|
||||||
await loadSites();
|
|
||||||
} catch (err) {
|
|
||||||
console.log("[load webfix]", err.message);
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
}, [loadSites]);
|
|
||||||
|
|
||||||
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="contained"
|
|
||||||
disabled={showAdd}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
setShowAdd(true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{i18n("add")}
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<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>
|
|
||||||
|
|
||||||
{showAdd && <WebfixFields webfix={webfix} setShow={setShowAdd} />}
|
|
||||||
|
|
||||||
{webfix.list?.length > 0 && (
|
|
||||||
<Box>
|
|
||||||
{webfix.list.map((rule) => (
|
|
||||||
<WebfixAccordion key={rule.pattern} rule={rule} webfix={webfix} />
|
|
||||||
))}
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{setting.injectWebfix && (
|
|
||||||
<Box>
|
|
||||||
{loading ? (
|
|
||||||
<center>
|
|
||||||
<CircularProgress size={16} />
|
|
||||||
</center>
|
|
||||||
) : (
|
|
||||||
sites.map((rule) => (
|
|
||||||
<WebfixAccordion key={rule.pattern} rule={rule} />
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -18,7 +18,6 @@ import Stack from "@mui/material/Stack";
|
|||||||
import { adaptScript } from "../../libs/gm";
|
import { adaptScript } from "../../libs/gm";
|
||||||
import Alert from "@mui/material/Alert";
|
import Alert from "@mui/material/Alert";
|
||||||
import Apis from "./Apis";
|
import Apis from "./Apis";
|
||||||
import Webfix from "./Webfix";
|
|
||||||
import InputSetting from "./InputSetting";
|
import InputSetting from "./InputSetting";
|
||||||
import Tranbox from "./Tranbox";
|
import Tranbox from "./Tranbox";
|
||||||
import FavWords from "./FavWords";
|
import FavWords from "./FavWords";
|
||||||
@@ -41,7 +40,7 @@ export default function Options() {
|
|||||||
setError(
|
setError(
|
||||||
`The version of the local script(v${version}) is not the latest version(v${process.env.REACT_APP_VERSION}). 本地脚本之版本(v${version})非最新版(v${process.env.REACT_APP_VERSION})。`
|
`The version of the local script(v${version}) is not the latest version(v${process.env.REACT_APP_VERSION}). 本地脚本之版本(v${version})非最新版(v${process.env.REACT_APP_VERSION})。`
|
||||||
);
|
);
|
||||||
break;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (eventName) {
|
if (eventName) {
|
||||||
@@ -49,9 +48,6 @@ export default function Options() {
|
|||||||
adaptScript(eventName);
|
adaptScript(eventName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 同步数据
|
|
||||||
await trySyncSettingAndRules();
|
|
||||||
setReady(true);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,16 +55,16 @@ export default function Options() {
|
|||||||
setError(
|
setError(
|
||||||
"Time out. Please confirm whether to install or enable KISS Translator GreaseMonkey script? 连接超时,请检查是否安装或启用简约翻译油猴脚本。"
|
"Time out. Please confirm whether to install or enable KISS Translator GreaseMonkey script? 连接超时,请检查是否安装或启用简约翻译油猴脚本。"
|
||||||
);
|
);
|
||||||
break;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await sleep(1000);
|
await sleep(1000);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// 同步数据
|
|
||||||
await trySyncSettingAndRules();
|
|
||||||
setReady(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 同步数据
|
||||||
|
await trySyncSettingAndRules();
|
||||||
|
setReady(true);
|
||||||
})();
|
})();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -85,15 +81,9 @@ export default function Options() {
|
|||||||
<Link href={process.env.REACT_APP_USERSCRIPT_DOWNLOADURL}>
|
<Link href={process.env.REACT_APP_USERSCRIPT_DOWNLOADURL}>
|
||||||
Install/Update Userscript for Tampermonkey/Violentmonkey
|
Install/Update Userscript for Tampermonkey/Violentmonkey
|
||||||
</Link>
|
</Link>
|
||||||
{/* <Link href={process.env.REACT_APP_USERSCRIPT_DOWNLOADURL2}>
|
|
||||||
Install/Update Userscript for Tampermonkey/Violentmonkey 2
|
|
||||||
</Link> */}
|
|
||||||
<Link href={process.env.REACT_APP_USERSCRIPT_IOS_DOWNLOADURL}>
|
<Link href={process.env.REACT_APP_USERSCRIPT_IOS_DOWNLOADURL}>
|
||||||
Install/Update Userscript for iOS Safari
|
Install/Update Userscript for iOS Safari
|
||||||
</Link>
|
</Link>
|
||||||
{/* <Link href={process.env.REACT_APP_USERSCRIPT_IOS_DOWNLOADURL2}>
|
|
||||||
Install/Update Userscript for iOS Safari 2
|
|
||||||
</Link> */}
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</center>
|
</center>
|
||||||
);
|
);
|
||||||
@@ -125,7 +115,6 @@ export default function Options() {
|
|||||||
<Route path="tranbox" element={<Tranbox />} />
|
<Route path="tranbox" element={<Tranbox />} />
|
||||||
<Route path="apis" element={<Apis />} />
|
<Route path="apis" element={<Apis />} />
|
||||||
<Route path="sync" element={<SyncSetting />} />
|
<Route path="sync" element={<SyncSetting />} />
|
||||||
<Route path="webfix" element={<Webfix />} />
|
|
||||||
<Route path="words" element={<FavWords />} />
|
<Route path="words" element={<FavWords />} />
|
||||||
<Route path="about" element={<About />} />
|
<Route path="about" element={<About />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import Box from "@mui/material/Box";
|
|
||||||
import IconButton from "@mui/material/IconButton";
|
import IconButton from "@mui/material/IconButton";
|
||||||
import CloseIcon from "@mui/icons-material/Close";
|
import CloseIcon from "@mui/icons-material/Close";
|
||||||
import HomeIcon from "@mui/icons-material/Home";
|
import HomeIcon from "@mui/icons-material/Home";
|
||||||
import Stack from "@mui/material/Stack";
|
import Stack from "@mui/material/Stack";
|
||||||
import DarkModeButton from "../Options/DarkModeButton";
|
import DarkModeButton from "../Options/DarkModeButton";
|
||||||
|
import Typography from "@mui/material/Typography";
|
||||||
|
|
||||||
export default function Header({ setShowPopup }) {
|
export default function Header({ setShowPopup }) {
|
||||||
const handleHomepage = () => {
|
const handleHomepage = () => {
|
||||||
@@ -21,14 +21,16 @@ export default function Header({ setShowPopup }) {
|
|||||||
<IconButton onClick={handleHomepage}>
|
<IconButton onClick={handleHomepage}>
|
||||||
<HomeIcon />
|
<HomeIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Box
|
<Typography
|
||||||
|
component="div"
|
||||||
sx={{
|
sx={{
|
||||||
userSelect: "none",
|
userSelect: "none",
|
||||||
WebkitUserSelect: "none",
|
WebkitUserSelect: "none",
|
||||||
|
fontWeight: "bold",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{`${process.env.REACT_APP_NAME} v${process.env.REACT_APP_VERSION}`}
|
{`${process.env.REACT_APP_NAME} v${process.env.REACT_APP_VERSION}`}
|
||||||
</Box>
|
</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
{setShowPopup ? (
|
{setShowPopup ? (
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import MenuItem from "@mui/material/MenuItem";
|
|||||||
import FormControlLabel from "@mui/material/FormControlLabel";
|
import FormControlLabel from "@mui/material/FormControlLabel";
|
||||||
import Switch from "@mui/material/Switch";
|
import Switch from "@mui/material/Switch";
|
||||||
import Button from "@mui/material/Button";
|
import Button from "@mui/material/Button";
|
||||||
import { sendBgMsg, sendTabMsg, getTabInfo } from "../../libs/msg";
|
import { sendBgMsg, sendTabMsg, getCurTab } from "../../libs/msg";
|
||||||
import { browser } from "../../libs/browser";
|
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";
|
||||||
@@ -18,19 +18,21 @@ import {
|
|||||||
MSG_TRANS_PUTRULE,
|
MSG_TRANS_PUTRULE,
|
||||||
MSG_OPEN_OPTIONS,
|
MSG_OPEN_OPTIONS,
|
||||||
MSG_SAVE_RULE,
|
MSG_SAVE_RULE,
|
||||||
|
MSG_COMMAND_SHORTCUTS,
|
||||||
OPT_TRANS_ALL,
|
OPT_TRANS_ALL,
|
||||||
OPT_LANGS_FROM,
|
OPT_LANGS_FROM,
|
||||||
OPT_LANGS_TO,
|
OPT_LANGS_TO,
|
||||||
OPT_STYLE_ALL,
|
OPT_STYLE_ALL,
|
||||||
OPT_STYLE_USE_COLOR,
|
|
||||||
} from "../../config";
|
} from "../../config";
|
||||||
import { sendIframeMsg } from "../../libs/iframe";
|
import { sendIframeMsg } from "../../libs/iframe";
|
||||||
import { saveRule } from "../../libs/rules";
|
import { saveRule } from "../../libs/rules";
|
||||||
import { tryClearCaches } from "../../libs";
|
import { tryClearCaches } from "../../libs";
|
||||||
|
import { kissLog } from "../../libs/log";
|
||||||
|
|
||||||
export default function Popup({ setShowPopup, translator: tran }) {
|
export default function Popup({ setShowPopup, translator: tran }) {
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const [rule, setRule] = useState(tran?.rule);
|
const [rule, setRule] = useState(tran?.rule);
|
||||||
|
const [commands, setCommands] = useState({});
|
||||||
|
|
||||||
const handleOpenSetting = () => {
|
const handleOpenSetting = () => {
|
||||||
if (!tran) {
|
if (!tran) {
|
||||||
@@ -54,7 +56,7 @@ export default function Popup({ setShowPopup, translator: tran }) {
|
|||||||
sendIframeMsg(MSG_TRANS_TOGGLE);
|
sendIframeMsg(MSG_TRANS_TOGGLE);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("[toggle trans]", err);
|
kissLog(err, "toggle trans");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -70,7 +72,7 @@ export default function Popup({ setShowPopup, translator: tran }) {
|
|||||||
sendIframeMsg(MSG_TRANS_PUTRULE, { [name]: value });
|
sendIframeMsg(MSG_TRANS_PUTRULE, { [name]: value });
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("[update rule]", err);
|
kissLog(err, "update rule");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -82,17 +84,17 @@ export default function Popup({ setShowPopup, translator: tran }) {
|
|||||||
try {
|
try {
|
||||||
let href = window.location.href;
|
let href = window.location.href;
|
||||||
if (!tran) {
|
if (!tran) {
|
||||||
const tab = await getTabInfo();
|
const tab = await getCurTab();
|
||||||
href = tab.url;
|
href = tab.url;
|
||||||
}
|
}
|
||||||
const newRule = { ...rule, pattern: href };
|
const newRule = { ...rule, pattern: href.split("/")[2] };
|
||||||
if (isExt && tran) {
|
if (isExt && tran) {
|
||||||
sendBgMsg(MSG_SAVE_RULE, newRule);
|
sendBgMsg(MSG_SAVE_RULE, newRule);
|
||||||
} else {
|
} else {
|
||||||
saveRule(newRule);
|
saveRule(newRule);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("[save rule]", err);
|
kissLog(err, "save rule");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -107,7 +109,31 @@ export default function Popup({ setShowPopup, translator: tran }) {
|
|||||||
setRule(res.data);
|
setRule(res.data);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("[query rule]", err);
|
kissLog(err, "query rule");
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}, [tran]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const commands = {};
|
||||||
|
if (isExt) {
|
||||||
|
const res = await sendBgMsg(MSG_COMMAND_SHORTCUTS);
|
||||||
|
res.forEach(({ name, shortcut }) => {
|
||||||
|
commands[name] = shortcut;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const shortcuts = tran.setting.shortcuts;
|
||||||
|
if (shortcuts) {
|
||||||
|
Object.entries(shortcuts).forEach(([key, val]) => {
|
||||||
|
commands[key] = val.join("+");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setCommands(commands);
|
||||||
|
} catch (err) {
|
||||||
|
kissLog(err, "query cmds");
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}, [tran]);
|
}, [tran]);
|
||||||
@@ -130,7 +156,7 @@ export default function Popup({ setShowPopup, translator: tran }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { transOpen, translator, fromLang, toLang, textStyle, bgColor } = rule;
|
const { transOpen, translator, fromLang, toLang, textStyle } = rule;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box minWidth={300}>
|
<Box minWidth={300}>
|
||||||
@@ -154,13 +180,12 @@ export default function Popup({ setShowPopup, translator: tran }) {
|
|||||||
onChange={handleTransToggle}
|
onChange={handleTransToggle}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
label={i18n("translate_alt")}
|
label={
|
||||||
|
commands["toggleTranslate"]
|
||||||
|
? `${i18n("translate_alt")}(${commands["toggleTranslate"]})`
|
||||||
|
: i18n("translate_alt")
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
{!isExt && (
|
|
||||||
<Button variant="text" onClick={handleClearCache}>
|
|
||||||
{i18n("clear_cache")}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
@@ -217,7 +242,11 @@ export default function Popup({ setShowPopup, translator: tran }) {
|
|||||||
size="small"
|
size="small"
|
||||||
value={textStyle}
|
value={textStyle}
|
||||||
name="textStyle"
|
name="textStyle"
|
||||||
label={i18n("text_style_alt")}
|
label={
|
||||||
|
commands["toggleStyle"]
|
||||||
|
? `${i18n("text_style_alt")}(${commands["toggleStyle"]})`
|
||||||
|
: i18n("text_style_alt")
|
||||||
|
}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
>
|
>
|
||||||
{OPT_STYLE_ALL.map((item) => (
|
{OPT_STYLE_ALL.map((item) => (
|
||||||
@@ -227,7 +256,7 @@ export default function Popup({ setShowPopup, translator: tran }) {
|
|||||||
))}
|
))}
|
||||||
</TextField>
|
</TextField>
|
||||||
|
|
||||||
{OPT_STYLE_USE_COLOR.includes(textStyle) && (
|
{/* {OPT_STYLE_USE_COLOR.includes(textStyle) && (
|
||||||
<TextField
|
<TextField
|
||||||
size="small"
|
size="small"
|
||||||
name="bgColor"
|
name="bgColor"
|
||||||
@@ -235,7 +264,7 @@ export default function Popup({ setShowPopup, translator: tran }) {
|
|||||||
label={i18n("bg_color")}
|
label={i18n("bg_color")}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
)}
|
)} */}
|
||||||
|
|
||||||
<Stack
|
<Stack
|
||||||
direction="row"
|
direction="row"
|
||||||
@@ -246,6 +275,11 @@ export default function Popup({ setShowPopup, translator: tran }) {
|
|||||||
<Button variant="text" onClick={handleSaveRule}>
|
<Button variant="text" onClick={handleSaveRule}>
|
||||||
{i18n("save_rule")}
|
{i18n("save_rule")}
|
||||||
</Button>
|
</Button>
|
||||||
|
{!isExt && (
|
||||||
|
<Button variant="text" onClick={handleClearCache}>
|
||||||
|
{i18n("clear_cache")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
<Button variant="text" onClick={handleOpenSetting}>
|
<Button variant="text" onClick={handleOpenSetting}>
|
||||||
{i18n("setting")}
|
{i18n("setting")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
29
src/views/Selection/AudioBtn.js
Normal file
29
src/views/Selection/AudioBtn.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import IconButton from "@mui/material/IconButton";
|
||||||
|
import VolumeUpIcon from "@mui/icons-material/VolumeUp";
|
||||||
|
import { useTextAudio } from "../../hooks/Audio";
|
||||||
|
|
||||||
|
export default function AudioBtn({ text, lan = "uk" }) {
|
||||||
|
const { error, ready, playing, onPlay } = useTextAudio(text, lan);
|
||||||
|
|
||||||
|
if (error || !ready) {
|
||||||
|
return (
|
||||||
|
<IconButton disabled size="small">
|
||||||
|
<VolumeUpIcon fontSize="inherit" />
|
||||||
|
</IconButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (playing) {
|
||||||
|
return (
|
||||||
|
<IconButton color="primary" size="small">
|
||||||
|
<VolumeUpIcon fontSize="inherit" />
|
||||||
|
</IconButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IconButton onClick={onPlay} size="small">
|
||||||
|
<VolumeUpIcon fontSize="inherit" />
|
||||||
|
</IconButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -5,9 +5,9 @@ import { useState } from "react";
|
|||||||
|
|
||||||
export default function CopyBtn({ text }) {
|
export default function CopyBtn({ text }) {
|
||||||
const [copied, setCopied] = useState(false);
|
const [copied, setCopied] = useState(false);
|
||||||
const handleClick = (e) => {
|
const handleClick = async (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
navigator.clipboard.writeText(text);
|
await navigator.clipboard.writeText(text);
|
||||||
setCopied(true);
|
setCopied(true);
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
|
|||||||
@@ -1,64 +1,115 @@
|
|||||||
import Box from "@mui/material/Box";
|
import { useState, useEffect } from "react";
|
||||||
import Chip from "@mui/material/Chip";
|
|
||||||
import Stack from "@mui/material/Stack";
|
import Stack from "@mui/material/Stack";
|
||||||
import FavBtn from "./FavBtn";
|
import FavBtn from "./FavBtn";
|
||||||
|
import Typography from "@mui/material/Typography";
|
||||||
|
import AudioBtn from "./AudioBtn";
|
||||||
|
import CircularProgress from "@mui/material/CircularProgress";
|
||||||
|
import Alert from "@mui/material/Alert";
|
||||||
|
import { OPT_TRANS_BAIDU, PHONIC_MAP } from "../../config";
|
||||||
|
import { apiTranslate } from "../../apis";
|
||||||
|
import { isValidWord } from "../../libs/utils";
|
||||||
|
import CopyBtn from "./CopyBtn";
|
||||||
|
|
||||||
const exchangeMap = {
|
export default function DictCont({ text }) {
|
||||||
word_third: "第三人称单数",
|
const [loading, setLoading] = useState(true);
|
||||||
word_ing: "现在分词",
|
const [error, setError] = useState("");
|
||||||
word_done: "过去式",
|
const [dictResult, setDictResult] = useState(null);
|
||||||
word_past: "过去分词",
|
|
||||||
word_pl: "复数",
|
|
||||||
word_proto: "词源",
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function DictCont({ dictResult }) {
|
useEffect(() => {
|
||||||
if (!dictResult) {
|
(async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
setError("");
|
||||||
|
setDictResult(null);
|
||||||
|
|
||||||
|
if (!isValidWord(text)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dictRes = await apiTranslate({
|
||||||
|
text,
|
||||||
|
translator: OPT_TRANS_BAIDU,
|
||||||
|
fromLang: "en",
|
||||||
|
toLang: "zh-CN",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (dictRes[2]?.type === 1) {
|
||||||
|
setDictResult(JSON.parse(dictRes[2].result));
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
setError(err.message);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}, [text]);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return <Alert severity="error">{error}</Alert>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <CircularProgress size={16} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!text || !dictResult) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const copyText = [
|
||||||
|
dictResult.src,
|
||||||
|
dictResult.voice
|
||||||
|
?.map(Object.entries)
|
||||||
|
.map((item) => item[0])
|
||||||
|
.map(([key, val]) => `${PHONIC_MAP[key]?.[0] || key} ${val}`)
|
||||||
|
.join(" "),
|
||||||
|
dictResult.content[0].mean
|
||||||
|
.map(({ pre, cont }) => {
|
||||||
|
return `${pre ? `[${pre}] ` : ""}${Object.keys(cont).join("; ")}`;
|
||||||
|
})
|
||||||
|
.join("\n"),
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Stack className="KT-transbox-dict" spacing={1}>
|
||||||
<Stack
|
<Stack direction="row" justifyContent="space-between">
|
||||||
direction="row"
|
<Typography variant="subtitle1" style={{ fontWeight: "bold" }}>
|
||||||
justifyContent="space-between"
|
{dictResult.src}
|
||||||
alignItems="flex-start"
|
</Typography>
|
||||||
>
|
<Stack direction="row" justifyContent="space-between">
|
||||||
<div style={{ fontWeight: "bold" }}>
|
<CopyBtn text={copyText} />
|
||||||
{dictResult.simple_means?.word_name}
|
<FavBtn word={dictResult.src} />
|
||||||
</div>
|
</Stack>
|
||||||
<FavBtn word={dictResult.simple_means?.word_name} />
|
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
{dictResult.simple_means?.symbols?.map(({ ph_en, ph_am, parts }, idx) => (
|
<Typography component="div">
|
||||||
<div key={idx}>
|
<Typography component="div">
|
||||||
{(ph_en || ph_am) && (
|
{dictResult.voice
|
||||||
<div>{`英 /${ph_en || ""}/ 美 /${ph_am || ""}/`}</div>
|
?.map(Object.entries)
|
||||||
)}
|
.map((item) => item[0])
|
||||||
<ul style={{ margin: "0.5em 0" }}>
|
.map(([key, val]) => (
|
||||||
{parts.map(({ part, means }, idx) => (
|
<Typography
|
||||||
<li key={idx}>
|
component="div"
|
||||||
{part ? `[${part}] ${means.join("; ")}` : means.join("; ")}
|
key={key}
|
||||||
</li>
|
style={{ display: "inline-block" }}
|
||||||
|
>
|
||||||
|
<Typography component="span">{`${
|
||||||
|
PHONIC_MAP[key]?.[0] || key
|
||||||
|
} ${val}`}</Typography>
|
||||||
|
<AudioBtn text={dictResult.src} lan={PHONIC_MAP[key]?.[1]} />
|
||||||
|
</Typography>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</Typography>
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
|
|
||||||
<div>
|
<Typography component="ul">
|
||||||
{Object.entries(dictResult.simple_means?.exchange || {})
|
{dictResult.content[0].mean.map(({ pre, cont }, idx) => (
|
||||||
.map(([key, val]) => `${exchangeMap[key] || key}: ${val.join(", ")}`)
|
<Typography component="li" key={idx}>
|
||||||
.join("; ")}
|
{pre && `[${pre}] `}
|
||||||
</div>
|
{Object.keys(cont).join("; ")}
|
||||||
|
</Typography>
|
||||||
<Stack direction="row" spacing={1} flexWrap="wrap" useFlexGap>
|
|
||||||
{Object.values(dictResult.simple_means?.tags || {})
|
|
||||||
.flat()
|
|
||||||
.filter((item) => item)
|
|
||||||
.map((item) => (
|
|
||||||
<Chip label={item} size="small" />
|
|
||||||
))}
|
))}
|
||||||
</Stack>
|
</Typography>
|
||||||
</Box>
|
</Typography>
|
||||||
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import Paper from "@mui/material/Paper";
|
import Paper from "@mui/material/Paper";
|
||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
|
import { isMobile } from "../../libs/mobile";
|
||||||
|
|
||||||
function Pointer({
|
function Pointer({
|
||||||
direction,
|
direction,
|
||||||
@@ -16,21 +17,23 @@ function Pointer({
|
|||||||
const [origin, setOrigin] = useState(null);
|
const [origin, setOrigin] = useState(null);
|
||||||
|
|
||||||
function handlePointerDown(e) {
|
function handlePointerDown(e) {
|
||||||
e.target.setPointerCapture(e.pointerId);
|
!isMobile && e.target.setPointerCapture(e.pointerId);
|
||||||
|
const { clientX, clientY } = isMobile ? e.targetTouches[0] : e;
|
||||||
setOrigin({
|
setOrigin({
|
||||||
x: position.x,
|
x: position.x,
|
||||||
y: position.y,
|
y: position.y,
|
||||||
w: size.w,
|
w: size.w,
|
||||||
h: size.h,
|
h: size.h,
|
||||||
clientX: e.clientX,
|
clientX,
|
||||||
clientY: e.clientY,
|
clientY,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePointerMove(e) {
|
function handlePointerMove(e) {
|
||||||
|
const { clientX, clientY } = isMobile ? e.targetTouches[0] : e;
|
||||||
if (origin) {
|
if (origin) {
|
||||||
const dx = e.clientX - origin.clientX;
|
const dx = clientX - origin.clientX;
|
||||||
const dy = e.clientY - origin.clientY;
|
const dy = clientY - origin.clientY;
|
||||||
let x = position.x;
|
let x = position.x;
|
||||||
let y = position.y;
|
let y = position.y;
|
||||||
let w = size.w;
|
let w = size.w;
|
||||||
@@ -101,16 +104,24 @@ function Pointer({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handlePointerUp(e) {
|
function handlePointerUp(e) {
|
||||||
|
e.stopPropagation();
|
||||||
setOrigin(null);
|
setOrigin(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const touchProps = isMobile
|
||||||
|
? {
|
||||||
|
onTouchStart: handlePointerDown,
|
||||||
|
onTouchMove: handlePointerMove,
|
||||||
|
onTouchEnd: handlePointerUp,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
onPointerDown: handlePointerDown,
|
||||||
|
onPointerMove: handlePointerMove,
|
||||||
|
onPointerUp: handlePointerUp,
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div {...props} {...touchProps}>
|
||||||
{...props}
|
|
||||||
onPointerDown={handlePointerDown}
|
|
||||||
onPointerMove={handlePointerMove}
|
|
||||||
onPointerUp={handlePointerUp}
|
|
||||||
>
|
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -137,6 +148,7 @@ export default function DraggableResizable({
|
|||||||
},
|
},
|
||||||
onChangeSize,
|
onChangeSize,
|
||||||
onChangePosition,
|
onChangePosition,
|
||||||
|
...props
|
||||||
}) {
|
}) {
|
||||||
const lineWidth = 4;
|
const lineWidth = 4;
|
||||||
const [position, setPosition] = useState(defaultPosition);
|
const [position, setPosition] = useState(defaultPosition);
|
||||||
@@ -161,7 +173,9 @@ export default function DraggableResizable({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
|
className="KT-draggable"
|
||||||
style={{
|
style={{
|
||||||
|
touchAction: "none",
|
||||||
position: "fixed",
|
position: "fixed",
|
||||||
left: position.x,
|
left: position.x,
|
||||||
top: position.y,
|
top: position.y,
|
||||||
@@ -170,6 +184,7 @@ export default function DraggableResizable({
|
|||||||
gridTemplateRows: `${lineWidth * 2}px auto ${lineWidth * 2}px`,
|
gridTemplateRows: `${lineWidth * 2}px auto ${lineWidth * 2}px`,
|
||||||
zIndex: 2147483647,
|
zIndex: 2147483647,
|
||||||
}}
|
}}
|
||||||
|
{...props}
|
||||||
>
|
>
|
||||||
<Pointer
|
<Pointer
|
||||||
direction="TopLeft"
|
direction="TopLeft"
|
||||||
@@ -205,11 +220,17 @@ export default function DraggableResizable({
|
|||||||
}}
|
}}
|
||||||
{...opts}
|
{...opts}
|
||||||
/>
|
/>
|
||||||
<Paper elevation={4}>
|
<Paper className="KT-draggable-body" elevation={4}>
|
||||||
<Pointer direction="Header" style={{ cursor: "move" }} {...opts}>
|
<Pointer
|
||||||
|
className="KT-draggable-header"
|
||||||
|
direction="Header"
|
||||||
|
style={{ cursor: "move" }}
|
||||||
|
{...opts}
|
||||||
|
>
|
||||||
{header}
|
{header}
|
||||||
</Pointer>
|
</Pointer>
|
||||||
<div
|
<Box
|
||||||
|
className="KT-draggable-container"
|
||||||
style={{
|
style={{
|
||||||
width: size.w,
|
width: size.w,
|
||||||
height: size.h,
|
height: size.h,
|
||||||
@@ -217,7 +238,7 @@ export default function DraggableResizable({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
<Pointer
|
<Pointer
|
||||||
direction="Right"
|
direction="Right"
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import FavoriteIcon from "@mui/icons-material/Favorite";
|
|||||||
import FavoriteBorderIcon from "@mui/icons-material/FavoriteBorder";
|
import FavoriteBorderIcon from "@mui/icons-material/FavoriteBorder";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useFavWords } from "../../hooks/FavWords";
|
import { useFavWords } from "../../hooks/FavWords";
|
||||||
|
import { kissLog } from "../../libs/log";
|
||||||
|
|
||||||
export default function FavBtn({ word }) {
|
export default function FavBtn({ word }) {
|
||||||
const { favWords, toggleFav } = useFavWords();
|
const { favWords, toggleFav } = useFavWords();
|
||||||
@@ -13,7 +14,7 @@ export default function FavBtn({ word }) {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
await toggleFav(word);
|
await toggleFav(word);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("[set fav]", err);
|
kissLog(err, "set fav");
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|||||||
35
src/views/Selection/SugCont.js
Normal file
35
src/views/Selection/SugCont.js
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import Typography from "@mui/material/Typography";
|
||||||
|
import { apiBaiduSuggest } from "../../apis";
|
||||||
|
import Stack from "@mui/material/Stack";
|
||||||
|
|
||||||
|
export default function SugCont({ text }) {
|
||||||
|
const [sugs, setSugs] = useState([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
setSugs(await apiBaiduSuggest(text));
|
||||||
|
} catch (err) {
|
||||||
|
// skip
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}, [text]);
|
||||||
|
|
||||||
|
if (sugs.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack className="KT-transbox-sug" spacing={1}>
|
||||||
|
{sugs.map(({ k, v }) => (
|
||||||
|
<Typography component="div" key={k}>
|
||||||
|
<Typography>{k}</Typography>
|
||||||
|
<Typography component="ul" style={{ margin: "0" }}>
|
||||||
|
<Typography component="li">{v}</Typography>
|
||||||
|
</Typography>
|
||||||
|
</Typography>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import { SettingProvider } from "../../hooks/Setting";
|
import { SettingProvider } from "../../hooks/Setting";
|
||||||
import ThemeProvider from "../../hooks/Theme";
|
import ThemeProvider from "../../hooks/Theme";
|
||||||
import DraggableResizable from "./DraggableResizable";
|
import DraggableResizable from "./DraggableResizable";
|
||||||
import Header from "../Popup/Header";
|
|
||||||
import Stack from "@mui/material/Stack";
|
import Stack from "@mui/material/Stack";
|
||||||
import TextField from "@mui/material/TextField";
|
import TextField from "@mui/material/TextField";
|
||||||
import MenuItem from "@mui/material/MenuItem";
|
import MenuItem from "@mui/material/MenuItem";
|
||||||
@@ -10,13 +9,73 @@ import Box from "@mui/material/Box";
|
|||||||
import Divider from "@mui/material/Divider";
|
import Divider from "@mui/material/Divider";
|
||||||
import IconButton from "@mui/material/IconButton";
|
import IconButton from "@mui/material/IconButton";
|
||||||
import DoneIcon from "@mui/icons-material/Done";
|
import DoneIcon from "@mui/icons-material/Done";
|
||||||
|
import DragIndicatorIcon from "@mui/icons-material/DragIndicator";
|
||||||
|
import UnfoldLessIcon from "@mui/icons-material/UnfoldLess";
|
||||||
|
import UnfoldMoreIcon from "@mui/icons-material/UnfoldMore";
|
||||||
|
import PushPinIcon from "@mui/icons-material/PushPin";
|
||||||
|
import PushPinOutlinedIcon from "@mui/icons-material/PushPinOutlined";
|
||||||
|
import CloseIcon from "@mui/icons-material/Close";
|
||||||
import { useI18n } from "../../hooks/I18n";
|
import { useI18n } from "../../hooks/I18n";
|
||||||
import { OPT_TRANS_ALL, OPT_LANGS_FROM, OPT_LANGS_TO } from "../../config";
|
import { OPT_TRANS_ALL, OPT_LANGS_FROM, OPT_LANGS_TO } from "../../config";
|
||||||
import { useState, useRef } from "react";
|
import { useState, useRef } from "react";
|
||||||
import TranCont from "./TranCont";
|
import TranCont from "./TranCont";
|
||||||
|
import DictCont from "./DictCont";
|
||||||
|
import SugCont from "./SugCont";
|
||||||
import CopyBtn from "./CopyBtn";
|
import CopyBtn from "./CopyBtn";
|
||||||
|
import { isValidWord } from "../../libs/utils";
|
||||||
|
|
||||||
function TranForm({ text, setText, tranboxSetting, transApis }) {
|
function Header({
|
||||||
|
setShowPopup,
|
||||||
|
simpleStyle,
|
||||||
|
setSimpleStyle,
|
||||||
|
hideClickAway,
|
||||||
|
setHideClickAway,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Box className="KT-transbox-header">
|
||||||
|
<Stack direction="row" justifyContent="space-between" alignItems="center">
|
||||||
|
<DragIndicatorIcon fontSize="small" />
|
||||||
|
<Stack direction="row" alignItems="center">
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
onClick={() => {
|
||||||
|
setHideClickAway((pre) => !pre);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{hideClickAway ? (
|
||||||
|
<PushPinOutlinedIcon fontSize="small" />
|
||||||
|
) : (
|
||||||
|
<PushPinIcon fontSize="small" />
|
||||||
|
)}
|
||||||
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
onClick={() => {
|
||||||
|
setSimpleStyle((pre) => !pre);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{simpleStyle ? (
|
||||||
|
<UnfoldMoreIcon fontSize="small" />
|
||||||
|
) : (
|
||||||
|
<UnfoldLessIcon fontSize="small" />
|
||||||
|
)}
|
||||||
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
onClick={() => {
|
||||||
|
setShowPopup(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CloseIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
<Divider />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function TranForm({ text, setText, tranboxSetting, transApis, simpleStyle }) {
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
|
|
||||||
const [editMode, setEditMode] = useState(false);
|
const [editMode, setEditMode] = useState(false);
|
||||||
@@ -27,130 +86,141 @@ function TranForm({ text, setText, tranboxSetting, transApis }) {
|
|||||||
const inputRef = useRef(null);
|
const inputRef = useRef(null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack sx={{ p: 2 }} spacing={2}>
|
<Stack
|
||||||
<Box>
|
className="KT-transbox-container"
|
||||||
<Grid container spacing={2} columns={12}>
|
sx={{ p: simpleStyle ? 1 : 2 }}
|
||||||
<Grid item xs={4} sm={4} md={4} lg={4}>
|
spacing={simpleStyle ? 1 : 2}
|
||||||
<TextField
|
>
|
||||||
select
|
{!simpleStyle && (
|
||||||
SelectProps={{ MenuProps: { disablePortal: true } }}
|
<>
|
||||||
fullWidth
|
<Box className="KT-transbox-select">
|
||||||
size="small"
|
<Grid container spacing={simpleStyle ? 1 : 2} columns={12}>
|
||||||
name="fromLang"
|
<Grid item xs={4} sm={4} md={4} lg={4}>
|
||||||
value={fromLang}
|
<TextField
|
||||||
label={i18n("from_lang")}
|
select
|
||||||
onChange={(e) => {
|
SelectProps={{ MenuProps: { disablePortal: true } }}
|
||||||
setFromLang(e.target.value);
|
fullWidth
|
||||||
}}
|
size="small"
|
||||||
>
|
name="fromLang"
|
||||||
{OPT_LANGS_FROM.map(([lang, name]) => (
|
value={fromLang}
|
||||||
<MenuItem key={lang} value={lang}>
|
label={i18n("from_lang")}
|
||||||
{name}
|
onChange={(e) => {
|
||||||
</MenuItem>
|
setFromLang(e.target.value);
|
||||||
))}
|
}}
|
||||||
</TextField>
|
>
|
||||||
</Grid>
|
{OPT_LANGS_FROM.map(([lang, name]) => (
|
||||||
<Grid item xs={4} sm={4} md={4} lg={4}>
|
<MenuItem key={lang} value={lang}>
|
||||||
<TextField
|
{name}
|
||||||
select
|
</MenuItem>
|
||||||
SelectProps={{ MenuProps: { disablePortal: true } }}
|
))}
|
||||||
fullWidth
|
</TextField>
|
||||||
size="small"
|
</Grid>
|
||||||
name="toLang"
|
<Grid item xs={4} sm={4} md={4} lg={4}>
|
||||||
value={toLang}
|
<TextField
|
||||||
label={i18n("to_lang")}
|
select
|
||||||
onChange={(e) => {
|
SelectProps={{ MenuProps: { disablePortal: true } }}
|
||||||
setToLang(e.target.value);
|
fullWidth
|
||||||
}}
|
size="small"
|
||||||
>
|
name="toLang"
|
||||||
{OPT_LANGS_TO.map(([lang, name]) => (
|
value={toLang}
|
||||||
<MenuItem key={lang} value={lang}>
|
label={i18n("to_lang")}
|
||||||
{name}
|
onChange={(e) => {
|
||||||
</MenuItem>
|
setToLang(e.target.value);
|
||||||
))}
|
}}
|
||||||
</TextField>
|
>
|
||||||
</Grid>
|
{OPT_LANGS_TO.map(([lang, name]) => (
|
||||||
<Grid item xs={4} sm={4} md={4} lg={4}>
|
<MenuItem key={lang} value={lang}>
|
||||||
<TextField
|
{name}
|
||||||
select
|
</MenuItem>
|
||||||
SelectProps={{ MenuProps: { disablePortal: true } }}
|
))}
|
||||||
fullWidth
|
</TextField>
|
||||||
size="small"
|
</Grid>
|
||||||
value={translator}
|
<Grid item xs={4} sm={4} md={4} lg={4}>
|
||||||
name="translator"
|
<TextField
|
||||||
label={i18n("translate_service")}
|
select
|
||||||
onChange={(e) => {
|
SelectProps={{ MenuProps: { disablePortal: true } }}
|
||||||
setTranslator(e.target.value);
|
fullWidth
|
||||||
}}
|
size="small"
|
||||||
>
|
value={translator}
|
||||||
{OPT_TRANS_ALL.map((item) => (
|
name="translator"
|
||||||
<MenuItem key={item} value={item}>
|
label={i18n("translate_service")}
|
||||||
{item}
|
onChange={(e) => {
|
||||||
</MenuItem>
|
setTranslator(e.target.value);
|
||||||
))}
|
}}
|
||||||
</TextField>
|
>
|
||||||
</Grid>
|
{OPT_TRANS_ALL.map((item) => (
|
||||||
</Grid>
|
<MenuItem key={item} value={item}>
|
||||||
</Box>
|
{item}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</TextField>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box className="KT-transbox-origin">
|
||||||
<TextField
|
<TextField
|
||||||
label={i18n("original_text")}
|
size="small"
|
||||||
inputRef={inputRef}
|
label={i18n("original_text")}
|
||||||
fullWidth
|
inputRef={inputRef}
|
||||||
multiline
|
fullWidth
|
||||||
value={editMode ? editText : text}
|
multiline
|
||||||
disabled={!editMode}
|
value={editMode ? editText : text}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setEditText(e.target.value);
|
setEditText(e.target.value);
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onFocus={() => {
|
||||||
setEditMode(true);
|
setEditMode(true);
|
||||||
setEditText(text);
|
setEditText(text);
|
||||||
const timer = setTimeout(() => {
|
}}
|
||||||
clearTimeout(timer);
|
onBlur={() => {
|
||||||
inputRef.current?.focus();
|
setEditMode(false);
|
||||||
}, 100);
|
setText(editText.trim());
|
||||||
}}
|
}}
|
||||||
onBlur={() => {
|
InputProps={{
|
||||||
setEditMode(false);
|
endAdornment: (
|
||||||
setText(editText.trim());
|
<Stack
|
||||||
}}
|
direction="row"
|
||||||
InputProps={{
|
sx={{
|
||||||
endAdornment: (
|
position: "absolute",
|
||||||
<Stack
|
right: 0,
|
||||||
direction="row"
|
top: 0,
|
||||||
sx={{
|
|
||||||
position: "absolute",
|
|
||||||
right: 0,
|
|
||||||
top: 0,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{editMode ? (
|
|
||||||
<IconButton
|
|
||||||
size="small"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DoneIcon fontSize="inherit" />
|
{editMode ? (
|
||||||
</IconButton>
|
<IconButton
|
||||||
) : (
|
size="small"
|
||||||
<CopyBtn text={text} />
|
onClick={(e) => {
|
||||||
)}
|
e.stopPropagation();
|
||||||
</Stack>
|
}}
|
||||||
),
|
>
|
||||||
}}
|
<DoneIcon fontSize="inherit" />
|
||||||
/>
|
</IconButton>
|
||||||
</Box>
|
) : (
|
||||||
|
<CopyBtn text={text} />
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
<TranCont
|
{(!simpleStyle || !isValidWord(text) || !toLang.startsWith("zh")) && (
|
||||||
text={text}
|
<TranCont
|
||||||
translator={translator}
|
text={text}
|
||||||
fromLang={fromLang}
|
translator={translator}
|
||||||
toLang={toLang}
|
fromLang={fromLang}
|
||||||
transApis={transApis}
|
toLang={toLang}
|
||||||
/>
|
toLang2={tranboxSetting.toLang2}
|
||||||
|
transApis={transApis}
|
||||||
|
simpleStyle={simpleStyle}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<DictCont text={text} />
|
||||||
|
<SugCont text={text} />
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -165,23 +235,37 @@ export default function TranBox({
|
|||||||
setBoxSize,
|
setBoxSize,
|
||||||
boxPosition,
|
boxPosition,
|
||||||
setBoxPosition,
|
setBoxPosition,
|
||||||
|
simpleStyle,
|
||||||
|
setSimpleStyle,
|
||||||
|
hideClickAway,
|
||||||
|
setHideClickAway,
|
||||||
|
extStyles,
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<SettingProvider>
|
<SettingProvider>
|
||||||
<ThemeProvider>
|
<ThemeProvider styles={extStyles}>
|
||||||
<DraggableResizable
|
<DraggableResizable
|
||||||
defaultPosition={boxPosition}
|
defaultPosition={boxPosition}
|
||||||
defaultSize={boxSize}
|
defaultSize={boxSize}
|
||||||
header={<Header setShowPopup={setShowBox} />}
|
header={
|
||||||
|
<Header
|
||||||
|
setShowPopup={setShowBox}
|
||||||
|
simpleStyle={simpleStyle}
|
||||||
|
setSimpleStyle={setSimpleStyle}
|
||||||
|
hideClickAway={hideClickAway}
|
||||||
|
setHideClickAway={setHideClickAway}
|
||||||
|
/>
|
||||||
|
}
|
||||||
onChangeSize={setBoxSize}
|
onChangeSize={setBoxSize}
|
||||||
onChangePosition={setBoxPosition}
|
onChangePosition={setBoxPosition}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
<Divider />
|
|
||||||
<TranForm
|
<TranForm
|
||||||
text={text}
|
text={text}
|
||||||
setText={setText}
|
setText={setText}
|
||||||
tranboxSetting={tranboxSetting}
|
tranboxSetting={tranboxSetting}
|
||||||
transApis={transApis}
|
transApis={transApis}
|
||||||
|
simpleStyle={simpleStyle}
|
||||||
/>
|
/>
|
||||||
</DraggableResizable>
|
</DraggableResizable>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
|
|||||||
@@ -1,22 +1,40 @@
|
|||||||
export default function TranBtn({ onClick, position, tranboxSetting }) {
|
import { isMobile } from "../../libs/mobile";
|
||||||
|
import { limitNumber } from "../../libs/utils";
|
||||||
|
|
||||||
|
export default function TranBtn({
|
||||||
|
onTrigger,
|
||||||
|
btnEvent,
|
||||||
|
position,
|
||||||
|
tranboxSetting,
|
||||||
|
}) {
|
||||||
|
const left = limitNumber(
|
||||||
|
position.x + tranboxSetting.btnOffsetX,
|
||||||
|
0,
|
||||||
|
window.innerWidth - 32
|
||||||
|
);
|
||||||
|
const top = limitNumber(
|
||||||
|
position.y + tranboxSetting.btnOffsetY,
|
||||||
|
0,
|
||||||
|
window.innerHeight - 32
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
className="KT-tranbtn"
|
||||||
style={{
|
style={{
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
|
// position: "absolute",
|
||||||
position: "fixed",
|
position: "fixed",
|
||||||
left: position.x + tranboxSetting.btnOffsetX,
|
left,
|
||||||
top: position.y + tranboxSetting.btnOffsetY,
|
top,
|
||||||
zIndex: 2147483647,
|
zIndex: 2147483647,
|
||||||
}}
|
}}
|
||||||
onClick={onClick}
|
{...{ [btnEvent]: onTrigger }}
|
||||||
onMouseUp={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
width="20"
|
width={isMobile ? "32" : "20"}
|
||||||
height="20"
|
height={isMobile ? "32" : "20"}
|
||||||
viewBox="0 0 32 32"
|
viewBox="0 0 32 32"
|
||||||
version="1.1"
|
version="1.1"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,28 +1,28 @@
|
|||||||
import TextField from "@mui/material/TextField";
|
import TextField from "@mui/material/TextField";
|
||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
import Alert from "@mui/material/Alert";
|
|
||||||
import CircularProgress from "@mui/material/CircularProgress";
|
import CircularProgress from "@mui/material/CircularProgress";
|
||||||
import Stack from "@mui/material/Stack";
|
import Stack from "@mui/material/Stack";
|
||||||
import { useI18n } from "../../hooks/I18n";
|
import { useI18n } from "../../hooks/I18n";
|
||||||
import { DEFAULT_TRANS_APIS, OPT_TRANS_BAIDU } from "../../config";
|
import { DEFAULT_TRANS_APIS } from "../../config";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { apiTranslate } from "../../apis";
|
import { apiTranslate, apiBaiduLangdetect } from "../../apis";
|
||||||
import { isValidWord } from "../../libs/utils";
|
|
||||||
import CopyBtn from "./CopyBtn";
|
import CopyBtn from "./CopyBtn";
|
||||||
import DictCont from "./DictCont";
|
import Typography from "@mui/material/Typography";
|
||||||
|
import Alert from "@mui/material/Alert";
|
||||||
|
|
||||||
export default function TranCont({
|
export default function TranCont({
|
||||||
text,
|
text,
|
||||||
translator,
|
translator,
|
||||||
fromLang,
|
fromLang,
|
||||||
toLang,
|
toLang,
|
||||||
|
toLang2 = "en",
|
||||||
transApis,
|
transApis,
|
||||||
|
simpleStyle,
|
||||||
}) {
|
}) {
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const [trText, setTrText] = useState("");
|
const [trText, setTrText] = useState("");
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
const [dictResult, setDictResult] = useState(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
@@ -30,7 +30,14 @@ export default function TranCont({
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
setTrText("");
|
setTrText("");
|
||||||
setError("");
|
setError("");
|
||||||
setDictResult(null);
|
|
||||||
|
let to = toLang;
|
||||||
|
if (toLang !== toLang2 && toLang2 !== "none") {
|
||||||
|
const detectLang = await apiBaiduLangdetect(text);
|
||||||
|
if (detectLang === toLang) {
|
||||||
|
to = toLang2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const apiSetting =
|
const apiSetting =
|
||||||
transApis[translator] || DEFAULT_TRANS_APIS[translator];
|
transApis[translator] || DEFAULT_TRANS_APIS[translator];
|
||||||
@@ -38,62 +45,58 @@ export default function TranCont({
|
|||||||
text,
|
text,
|
||||||
translator,
|
translator,
|
||||||
fromLang,
|
fromLang,
|
||||||
toLang,
|
toLang: to,
|
||||||
apiSetting,
|
apiSetting,
|
||||||
});
|
});
|
||||||
setTrText(tranRes[0]);
|
setTrText(tranRes[0]);
|
||||||
|
|
||||||
// 词典
|
|
||||||
if (isValidWord(text) && toLang.startsWith("zh")) {
|
|
||||||
if (fromLang === "en" && translator === OPT_TRANS_BAIDU) {
|
|
||||||
setDictResult(tranRes[2].dict_result);
|
|
||||||
} else {
|
|
||||||
const dictRes = await apiTranslate({
|
|
||||||
text,
|
|
||||||
translator: OPT_TRANS_BAIDU,
|
|
||||||
fromLang: "en",
|
|
||||||
toLang: "zh-CN",
|
|
||||||
});
|
|
||||||
setDictResult(dictRes[2].dict_result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err.message);
|
setError(err.message);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}, [text, translator, fromLang, toLang, transApis]);
|
}, [text, translator, fromLang, toLang, toLang2, transApis]);
|
||||||
|
|
||||||
|
if (simpleStyle) {
|
||||||
|
return (
|
||||||
|
<Box className="KT-transbox-target KT-transbox-target_simple">
|
||||||
|
{error ? (
|
||||||
|
<Alert severity="error">{error}</Alert>
|
||||||
|
) : loading ? (
|
||||||
|
<CircularProgress size={16} />
|
||||||
|
) : (
|
||||||
|
<Typography style={{ whiteSpace: "pre-line" }}>{trText}</Typography>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Box className="KT-transbox-target KT-transbox-target_default">
|
||||||
<Box>
|
<TextField
|
||||||
<TextField
|
size="small"
|
||||||
label={i18n("translated_text")}
|
label={i18n("translated_text")}
|
||||||
// disabled
|
// disabled
|
||||||
fullWidth
|
fullWidth
|
||||||
multiline
|
multiline
|
||||||
value={trText}
|
value={trText}
|
||||||
InputProps={{
|
helperText={error}
|
||||||
endAdornment: (
|
InputProps={{
|
||||||
<Stack
|
startAdornment: loading ? <CircularProgress size={16} /> : null,
|
||||||
direction="row"
|
endAdornment: (
|
||||||
sx={{
|
<Stack
|
||||||
position: "absolute",
|
direction="row"
|
||||||
right: 0,
|
sx={{
|
||||||
top: 0,
|
position: "absolute",
|
||||||
}}
|
right: 0,
|
||||||
>
|
top: 0,
|
||||||
<CopyBtn text={trText} />
|
}}
|
||||||
</Stack>
|
>
|
||||||
),
|
<CopyBtn text={trText} />
|
||||||
}}
|
</Stack>
|
||||||
/>
|
),
|
||||||
</Box>
|
}}
|
||||||
|
/>
|
||||||
{loading && <CircularProgress size={24} />}
|
</Box>
|
||||||
{error && <Alert severity="error">{error}</Alert>}
|
|
||||||
{dictResult && <DictCont dictResult={dictResult} />}
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,64 +1,180 @@
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect, useCallback, useMemo } from "react";
|
||||||
import TranBtn from "./TranBtn";
|
import TranBtn from "./TranBtn";
|
||||||
import TranBox from "./TranBox";
|
import TranBox from "./TranBox";
|
||||||
import { shortcutRegister } from "../../libs/shortcut";
|
import { shortcutRegister } from "../../libs/shortcut";
|
||||||
import { sleep } from "../../libs/utils";
|
import { sleep, limitNumber } from "../../libs/utils";
|
||||||
|
import { isGm, isExt } from "../../libs/client";
|
||||||
|
import {
|
||||||
|
MSG_OPEN_TRANBOX,
|
||||||
|
DEFAULT_TRANBOX_SHORTCUT,
|
||||||
|
OPT_TRANBOX_TRIGGER_CLICK,
|
||||||
|
OPT_TRANBOX_TRIGGER_HOVER,
|
||||||
|
OPT_TRANBOX_TRIGGER_SELECT,
|
||||||
|
} from "../../config";
|
||||||
|
import { isMobile } from "../../libs/mobile";
|
||||||
|
import { kissLog } from "../../libs/log";
|
||||||
|
|
||||||
|
export default function Slection({
|
||||||
|
contextMenuType,
|
||||||
|
tranboxSetting,
|
||||||
|
transApis,
|
||||||
|
}) {
|
||||||
|
const {
|
||||||
|
hideTranBtn = false,
|
||||||
|
simpleStyle: initSimpleStyle = false,
|
||||||
|
hideClickAway: initHideClickAway = false,
|
||||||
|
tranboxShortcut = DEFAULT_TRANBOX_SHORTCUT,
|
||||||
|
triggerMode = OPT_TRANBOX_TRIGGER_CLICK,
|
||||||
|
extStyles,
|
||||||
|
} = tranboxSetting;
|
||||||
|
|
||||||
|
const boxWidth =
|
||||||
|
isMobile || initSimpleStyle
|
||||||
|
? 300
|
||||||
|
: limitNumber(window.innerWidth, 300, 600);
|
||||||
|
const boxHeight =
|
||||||
|
isMobile || initSimpleStyle
|
||||||
|
? 200
|
||||||
|
: limitNumber(window.innerHeight, 200, 400);
|
||||||
|
|
||||||
export default function Slection({ tranboxSetting, transApis }) {
|
|
||||||
const [showBox, setShowBox] = useState(false);
|
const [showBox, setShowBox] = useState(false);
|
||||||
const [showBtn, setShowBtn] = useState(false);
|
const [showBtn, setShowBtn] = useState(false);
|
||||||
const [selectedText, setSelText] = useState("");
|
const [selectedText, setSelText] = useState("");
|
||||||
const [text, setText] = useState("");
|
const [text, setText] = useState("");
|
||||||
const [position, setPosition] = useState({ x: 0, y: 0 });
|
const [position, setPosition] = useState({ x: 0, y: 0 });
|
||||||
const [boxSize, setBoxSize] = useState({ w: 600, h: 400 });
|
const [boxSize, setBoxSize] = useState({
|
||||||
const [boxPosition, setBoxPosition] = useState({
|
w: boxWidth,
|
||||||
x: (window.innerWidth - 600) / 2,
|
h: boxHeight,
|
||||||
y: (window.innerHeight - 400) / 2,
|
|
||||||
});
|
});
|
||||||
|
const [boxPosition, setBoxPosition] = useState({
|
||||||
|
x: (window.innerWidth - boxWidth) / 2,
|
||||||
|
y: (window.innerHeight - boxHeight) / 2,
|
||||||
|
});
|
||||||
|
const [simpleStyle, setSimpleStyle] = useState(initSimpleStyle);
|
||||||
|
const [hideClickAway, setHideClickAway] = useState(initHideClickAway);
|
||||||
|
|
||||||
async function handleMouseup(e) {
|
const handleTrigger = useCallback(
|
||||||
await sleep(10);
|
(text) => {
|
||||||
|
setShowBtn(false);
|
||||||
|
setText(text || selectedText);
|
||||||
|
setShowBox(true);
|
||||||
|
},
|
||||||
|
[selectedText]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleTranbox = useCallback(() => {
|
||||||
|
setShowBtn(false);
|
||||||
|
|
||||||
const selectedText = window.getSelection()?.toString()?.trim() || "";
|
const selectedText = window.getSelection()?.toString()?.trim() || "";
|
||||||
if (!selectedText) {
|
if (!selectedText) {
|
||||||
setShowBtn(false);
|
setShowBox((pre) => !pre);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setSelText(selectedText);
|
setSelText(selectedText);
|
||||||
setShowBtn(true);
|
|
||||||
setPosition({ x: e.clientX, y: e.clientY });
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleClick = (e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
setShowBtn(false);
|
|
||||||
|
|
||||||
setText(selectedText);
|
setText(selectedText);
|
||||||
if (!showBox) {
|
setShowBox(true);
|
||||||
setShowBox(true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
window.addEventListener("mouseup", handleMouseup);
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener("mouseup", handleMouseup);
|
|
||||||
};
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
const btnEvent = useMemo(() => {
|
||||||
const clearShortcut = shortcutRegister(
|
if (isMobile) {
|
||||||
tranboxSetting.tranboxShortcut,
|
return "onTouchEnd";
|
||||||
() => {
|
} else if (triggerMode === OPT_TRANBOX_TRIGGER_HOVER) {
|
||||||
setShowBox((pre) => !pre);
|
return "onMouseOver";
|
||||||
}
|
}
|
||||||
);
|
return "onMouseUp";
|
||||||
|
}, [triggerMode]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function handleMouseup(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
await sleep(200);
|
||||||
|
|
||||||
|
const selectedText = window.getSelection()?.toString()?.trim() || "";
|
||||||
|
setSelText(selectedText);
|
||||||
|
if (!selectedText) {
|
||||||
|
setShowBtn(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (triggerMode === OPT_TRANBOX_TRIGGER_SELECT) {
|
||||||
|
handleTrigger(selectedText);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { clientX, clientY } = isMobile ? e.changedTouches[0] : e;
|
||||||
|
setShowBtn(!hideTranBtn);
|
||||||
|
setPosition({ x: clientX, y: clientY });
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: mobile support
|
||||||
|
// window.addEventListener("mouseup", handleMouseup);
|
||||||
|
window.addEventListener(isMobile ? "touchend" : "mouseup", handleMouseup);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener(
|
||||||
|
isMobile ? "touchend" : "mouseup",
|
||||||
|
handleMouseup
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}, [hideTranBtn, triggerMode, handleTrigger]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isExt) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const clearShortcut = shortcutRegister(tranboxShortcut, handleTranbox);
|
||||||
return () => {
|
return () => {
|
||||||
clearShortcut();
|
clearShortcut();
|
||||||
};
|
};
|
||||||
}, [tranboxSetting.tranboxShortcut, setShowBox]);
|
}, [tranboxShortcut, handleTranbox]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener(MSG_OPEN_TRANBOX, handleTranbox);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener(MSG_OPEN_TRANBOX, handleTranbox);
|
||||||
|
};
|
||||||
|
}, [handleTranbox]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isGm) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册菜单
|
||||||
|
try {
|
||||||
|
const menuCommandIds = [];
|
||||||
|
contextMenuType !== 0 &&
|
||||||
|
menuCommandIds.push(
|
||||||
|
GM.registerMenuCommand(
|
||||||
|
"Translate Selected Text",
|
||||||
|
(event) => {
|
||||||
|
handleTranbox();
|
||||||
|
},
|
||||||
|
"S"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
menuCommandIds.forEach((id) => {
|
||||||
|
GM.unregisterMenuCommand(id);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
kissLog(err, "registerMenuCommand");
|
||||||
|
}
|
||||||
|
}, [handleTranbox, contextMenuType]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (hideClickAway) {
|
||||||
|
const handleHideBox = () => {
|
||||||
|
setShowBox(false);
|
||||||
|
};
|
||||||
|
window.addEventListener("click", handleHideBox);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("click", handleHideBox);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [hideClickAway]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -73,6 +189,11 @@ export default function Slection({ tranboxSetting, transApis }) {
|
|||||||
tranboxSetting={tranboxSetting}
|
tranboxSetting={tranboxSetting}
|
||||||
transApis={transApis}
|
transApis={transApis}
|
||||||
setShowBox={setShowBox}
|
setShowBox={setShowBox}
|
||||||
|
simpleStyle={simpleStyle}
|
||||||
|
setSimpleStyle={setSimpleStyle}
|
||||||
|
hideClickAway={hideClickAway}
|
||||||
|
setHideClickAway={setHideClickAway}
|
||||||
|
extStyles={extStyles}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -80,7 +201,11 @@ export default function Slection({ tranboxSetting, transApis }) {
|
|||||||
<TranBtn
|
<TranBtn
|
||||||
position={position}
|
position={position}
|
||||||
tranboxSetting={tranboxSetting}
|
tranboxSetting={tranboxSetting}
|
||||||
onClick={handleClick}
|
btnEvent={btnEvent}
|
||||||
|
onTrigger={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleTrigger();
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
Reference in New Issue
Block a user