Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d7e03ddaf | ||
|
|
1f213bf257 | ||
|
|
b38f079611 | ||
|
|
21e639cacd | ||
|
|
bdaf665b7c | ||
|
|
61a515c1d2 | ||
|
|
1b646df908 | ||
|
|
5550f939b2 | ||
|
|
b34fb5a600 | ||
|
|
c0dce5c0b1 | ||
|
|
d56bd2920f | ||
|
|
48ad100a64 | ||
|
|
ef07a172a9 | ||
|
|
f492d47719 | ||
|
|
ac8c07deb4 | ||
|
|
ca48ab639e | ||
|
|
7c5232c1a1 | ||
|
|
4fac7fdfe1 | ||
|
|
f7fc9560d5 | ||
|
|
f7ba744e7f |
2
.env
2
.env
@@ -2,7 +2,7 @@ GENERATE_SOURCEMAP=false
|
|||||||
|
|
||||||
REACT_APP_NAME=KISS Translator
|
REACT_APP_NAME=KISS Translator
|
||||||
REACT_APP_NAME_CN=简约翻译
|
REACT_APP_NAME_CN=简约翻译
|
||||||
REACT_APP_VERSION=2.0.3
|
REACT_APP_VERSION=2.0.4
|
||||||
|
|
||||||
REACT_APP_HOMEPAGE=https://github.com/fishjar/kiss-translator
|
REACT_APP_HOMEPAGE=https://github.com/fishjar/kiss-translator
|
||||||
|
|
||||||
|
|||||||
@@ -153,6 +153,10 @@ Custom APIs are very powerful and flexible, and can theoretically connect to any
|
|||||||
|
|
||||||
Example reference: [custom-api_v2.md](https://github.com/fishjar/kiss-translator/blob/master/custom-api_v2.md)
|
Example reference: [custom-api_v2.md](https://github.com/fishjar/kiss-translator/blob/master/custom-api_v2.md)
|
||||||
|
|
||||||
|
### How to directly access the Tampermonkey script settings page
|
||||||
|
|
||||||
|
Settings page address: https://fishjar.github.io/kiss-translator/options.html
|
||||||
|
|
||||||
## Future Plans
|
## Future Plans
|
||||||
|
|
||||||
This is a side project with no strict timeline. Community contributions are welcome. The following are preliminary feature directions:
|
This is a side project with no strict timeline. Community contributions are welcome. The following are preliminary feature directions:
|
||||||
|
|||||||
@@ -149,6 +149,10 @@
|
|||||||
|
|
||||||
示例参考: [custom-api_v2.md](https://github.com/fishjar/kiss-translator/blob/master/custom-api_v2.md)
|
示例参考: [custom-api_v2.md](https://github.com/fishjar/kiss-translator/blob/master/custom-api_v2.md)
|
||||||
|
|
||||||
|
### 如何直接进入油猴脚本设置页面
|
||||||
|
|
||||||
|
设置页面地址: https://fishjar.github.io/kiss-translator/options.html
|
||||||
|
|
||||||
## 未来规划
|
## 未来规划
|
||||||
|
|
||||||
本项目为业余开发,无严格时间表,欢迎社区共建。以下为初步设想的功能方向:
|
本项目为业余开发,无严格时间表,欢迎社区共建。以下为初步设想的功能方向:
|
||||||
|
|||||||
@@ -1,5 +1,44 @@
|
|||||||
# 自定义接口示例
|
# 自定义接口示例
|
||||||
|
|
||||||
|
## 默认接口规范
|
||||||
|
|
||||||
|
如果接口的请求数据和返回数据符合以下规范,
|
||||||
|
则无需填写 `Request Hook` 或 `Response Hook`。
|
||||||
|
|
||||||
|
Request body
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"texts": ["hello"], // 需要翻译的文本列表
|
||||||
|
"from":"auto", // 原文语言
|
||||||
|
"to": "zh-CN" // 目标语言
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Response
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"text": "你好", // 译文
|
||||||
|
"src": "en" // 原文语言
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
v2.0.4版后亦支持以下 Response 格式
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"translations": [ // 译文列表
|
||||||
|
{
|
||||||
|
"text": "你好", // 译文
|
||||||
|
"src": "en" // 原文语言
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## 谷歌翻译接口
|
## 谷歌翻译接口
|
||||||
|
|
||||||
> 此接口不支持聚合
|
> 此接口不支持聚合
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "kiss-translator",
|
"name": "kiss-translator",
|
||||||
"description": "A minimalist bilingual translation Extension & Greasemonkey Script",
|
"description": "A minimalist bilingual translation Extension & Greasemonkey Script",
|
||||||
"version": "2.0.3",
|
"version": "2.0.4",
|
||||||
"author": "Gabe<yugang2002@gmail.com>",
|
"author": "Gabe<yugang2002@gmail.com>",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -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": "2.0.3",
|
"version": "2.0.4",
|
||||||
"default_locale": "en",
|
"default_locale": "en",
|
||||||
"author": "Gabe<yugang2002@gmail.com>",
|
"author": "Gabe<yugang2002@gmail.com>",
|
||||||
"homepage_url": "https://github.com/fishjar/kiss-translator",
|
"homepage_url": "https://github.com/fishjar/kiss-translator",
|
||||||
|
|||||||
@@ -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": "2.0.3",
|
"version": "2.0.4",
|
||||||
"default_locale": "en",
|
"default_locale": "en",
|
||||||
"author": "Gabe<yugang2002@gmail.com>",
|
"author": "Gabe<yugang2002@gmail.com>",
|
||||||
"homepage_url": "https://github.com/fishjar/kiss-translator",
|
"homepage_url": "https://github.com/fishjar/kiss-translator",
|
||||||
|
|||||||
@@ -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": "2.0.3",
|
"version": "2.0.4",
|
||||||
"default_locale": "en",
|
"default_locale": "en",
|
||||||
"author": "Gabe<yugang2002@gmail.com>",
|
"author": "Gabe<yugang2002@gmail.com>",
|
||||||
"homepage_url": "https://github.com/fishjar/kiss-translator",
|
"homepage_url": "https://github.com/fishjar/kiss-translator",
|
||||||
|
|||||||
@@ -847,7 +847,7 @@ export const parseTransRes = async (
|
|||||||
}
|
}
|
||||||
return parseAIRes(modelMsg?.content);
|
return parseAIRes(modelMsg?.content);
|
||||||
case OPT_TRANS_CUSTOMIZE:
|
case OPT_TRANS_CUSTOMIZE:
|
||||||
return res?.map((item) => [item.text, item.src]);
|
return (res?.translations ?? res)?.map((item) => [item.text, item.src]);
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1160,9 +1160,9 @@ export const I18N = {
|
|||||||
zh_TW: `觸控設定`,
|
zh_TW: `觸控設定`,
|
||||||
},
|
},
|
||||||
touch_translate_shortcut: {
|
touch_translate_shortcut: {
|
||||||
zh: `触屏翻译快捷方式`,
|
zh: `触屏翻译快捷方式 (支持多选)`,
|
||||||
en: `Touch Translate Shortcut`,
|
en: `Touch Translate Shortcut (multiple supported)`,
|
||||||
zh_TW: `觸控翻譯捷徑`,
|
zh_TW: `觸控翻譯捷徑 (支援多選)`,
|
||||||
},
|
},
|
||||||
touch_tap_0: {
|
touch_tap_0: {
|
||||||
zh: `禁用`,
|
zh: `禁用`,
|
||||||
@@ -1349,6 +1349,11 @@ export const I18N = {
|
|||||||
en: `Transbox Follow Selection`,
|
en: `Transbox Follow Selection`,
|
||||||
zh_TW: `翻譯框跟隨選取文字`,
|
zh_TW: `翻譯框跟隨選取文字`,
|
||||||
},
|
},
|
||||||
|
tranbox_auto_height: {
|
||||||
|
zh: `翻译框自适应高度`,
|
||||||
|
en: `Translation box adaptive height`,
|
||||||
|
zh_TW: `翻譯框自適應高度`,
|
||||||
|
},
|
||||||
translate_start_hook: {
|
translate_start_hook: {
|
||||||
zh: `翻译开始钩子函数`,
|
zh: `翻译开始钩子函数`,
|
||||||
en: `Translate Start Hook`,
|
en: `Translate Start Hook`,
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ background: linear-gradient(
|
|||||||
export const DEFAULT_SELECTOR =
|
export const DEFAULT_SELECTOR =
|
||||||
"h1, h2, h3, h4, h5, h6, li, p, dd, blockquote, figcaption, label, legend";
|
"h1, h2, h3, h4, h5, h6, li, p, dd, blockquote, figcaption, label, legend";
|
||||||
export const DEFAULT_IGNORE_SELECTOR = "button, footer, pre, mark, nav";
|
export const DEFAULT_IGNORE_SELECTOR = "button, footer, pre, mark, nav";
|
||||||
export const DEFAULT_KEEP_SELECTOR = `a:has(code)`;
|
export const DEFAULT_KEEP_SELECTOR = `code, cite, math, .math, a:has(code)`;
|
||||||
export const DEFAULT_RULE = {
|
export const DEFAULT_RULE = {
|
||||||
pattern: "", // 匹配网址
|
pattern: "", // 匹配网址
|
||||||
selector: "", // 选择器
|
selector: "", // 选择器
|
||||||
@@ -211,7 +211,8 @@ const RULES_MAP = {
|
|||||||
},
|
},
|
||||||
"twitter.com, https://x.com": {
|
"twitter.com, https://x.com": {
|
||||||
selector: `[data-testid='tweetText']`,
|
selector: `[data-testid='tweetText']`,
|
||||||
keepSelector: `img, svg, span:has(a), div:has(a)`,
|
keepSelector: `img, svg, a, span:has(a), div:has(a)`,
|
||||||
|
ignoreSelector: `button, [data-testid='videoPlayer'], [role='group']`,
|
||||||
autoScan: `false`,
|
autoScan: `false`,
|
||||||
},
|
},
|
||||||
"www.youtube.com/live_chat": {
|
"www.youtube.com/live_chat": {
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ export const DEFAULT_TRANBOX_SETTING = {
|
|||||||
hideClickAway: false, // 是否点击外部关闭弹窗
|
hideClickAway: false, // 是否点击外部关闭弹窗
|
||||||
simpleStyle: false, // 是否简洁界面
|
simpleStyle: false, // 是否简洁界面
|
||||||
followSelection: false, // 翻译框是否跟随选中文本
|
followSelection: false, // 翻译框是否跟随选中文本
|
||||||
|
autoHeight: false, // 自适应高度
|
||||||
triggerMode: OPT_TRANBOX_TRIGGER_CLICK, // 触发翻译方式
|
triggerMode: OPT_TRANBOX_TRIGGER_CLICK, // 触发翻译方式
|
||||||
// extStyles: "", // 附加样式
|
// extStyles: "", // 附加样式
|
||||||
enDict: OPT_DICT_BING, // 英文词典
|
enDict: OPT_DICT_BING, // 英文词典
|
||||||
@@ -166,7 +167,8 @@ export const DEFAULT_SETTING = {
|
|||||||
shortcuts: DEFAULT_SHORTCUTS, // 快捷键
|
shortcuts: DEFAULT_SHORTCUTS, // 快捷键
|
||||||
inputRule: DEFAULT_INPUT_RULE, // 输入框设置
|
inputRule: DEFAULT_INPUT_RULE, // 输入框设置
|
||||||
tranboxSetting: DEFAULT_TRANBOX_SETTING, // 划词翻译设置
|
tranboxSetting: DEFAULT_TRANBOX_SETTING, // 划词翻译设置
|
||||||
touchTranslate: 2, // 触屏翻译 {5:单指双击,6:单指三击,7:双指双击}
|
// touchTranslate: 2, // 触屏翻译 {5:单指双击,6:单指三击,7:双指双击} (作废)
|
||||||
|
touchModes: [2], // 触屏翻译 {5:单指双击,6:单指三击,7:双指双击} (多选)
|
||||||
blacklist: DEFAULT_BLACKLIST.join(",\n"), // 禁用翻译名单
|
blacklist: DEFAULT_BLACKLIST.join(",\n"), // 禁用翻译名单
|
||||||
csplist: DEFAULT_CSPLIST.join(",\n"), // 禁用CSP名单
|
csplist: DEFAULT_CSPLIST.join(",\n"), // 禁用CSP名单
|
||||||
orilist: DEFAULT_ORILIST.join(",\n"), // 禁用CSP名单
|
orilist: DEFAULT_ORILIST.join(",\n"), // 禁用CSP名单
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export const STOKEY_RULES = `${APP_NAME}_rules_v${APP_VERSION[0]}`;
|
|||||||
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_TRANBOX = `${APP_NAME}_tranbox`;
|
||||||
export const STOKEY_RULESCACHE_PREFIX = `${APP_NAME}_rulescache_`;
|
export const STOKEY_RULESCACHE_PREFIX = `${APP_NAME}_rulescache_`;
|
||||||
|
|
||||||
export const CACHE_NAME = `${APP_NAME}_cache`;
|
export const CACHE_NAME = `${APP_NAME}_cache`;
|
||||||
|
|||||||
@@ -63,8 +63,8 @@ export const shortcutRegister = (targetKeys = [], fn, target = document) => {
|
|||||||
const targetKeySet = new Set(targetKeys);
|
const targetKeySet = new Set(targetKeys);
|
||||||
const onKeyDown = (pressedKeys, event) => {
|
const onKeyDown = (pressedKeys, event) => {
|
||||||
if (isSameSet(targetKeySet, pressedKeys)) {
|
if (isSameSet(targetKeySet, pressedKeys)) {
|
||||||
// event.preventDefault();
|
// event.preventDefault(); // 阻止浏览器的默认行为
|
||||||
event.stopPropagation();
|
// event.stopPropagation(); // 阻止事件继续(向父元素)冒泡
|
||||||
fn();
|
fn();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
STOKEY_RULES_OLD,
|
STOKEY_RULES_OLD,
|
||||||
STOKEY_WORDS,
|
STOKEY_WORDS,
|
||||||
STOKEY_FAB,
|
STOKEY_FAB,
|
||||||
|
STOKEY_TRANBOX,
|
||||||
STOKEY_SYNC,
|
STOKEY_SYNC,
|
||||||
STOKEY_MSAUTH,
|
STOKEY_MSAUTH,
|
||||||
STOKEY_BDAUTH,
|
STOKEY_BDAUTH,
|
||||||
@@ -135,6 +136,13 @@ export const getFabWithDefault = async () => (await getFab()) || {};
|
|||||||
export const setFab = (obj) => setObj(STOKEY_FAB, obj);
|
export const setFab = (obj) => setObj(STOKEY_FAB, obj);
|
||||||
export const putFab = (obj) => putObj(STOKEY_FAB, obj);
|
export const putFab = (obj) => putObj(STOKEY_FAB, obj);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* tranbox位置大小
|
||||||
|
*/
|
||||||
|
export const getTranBox = () => getObj(STOKEY_TRANBOX);
|
||||||
|
export const putTranBox = (obj) => putObj(STOKEY_TRANBOX, obj);
|
||||||
|
export const debouncePutTranBox = debounce(putTranBox, 300);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 数据同步
|
* 数据同步
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ const genStyles = ({
|
|||||||
// 虚线框
|
// 虚线框
|
||||||
[OPT_STYLE_DASHBOX]: `
|
[OPT_STYLE_DASHBOX]: `
|
||||||
border: 2px dashed ${bgColor || DEFAULT_COLOR};
|
border: 2px dashed ${bgColor || DEFAULT_COLOR};
|
||||||
display: inline-block;
|
display: block;
|
||||||
padding: 0.2em 0.4em;
|
padding: 0.2em 0.4em;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
`,
|
`,
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ export class Translator {
|
|||||||
"VIDEO",
|
"VIDEO",
|
||||||
]),
|
]),
|
||||||
INLINE: new Set([
|
INLINE: new Set([
|
||||||
"A",
|
// "A",
|
||||||
"ABBR",
|
"ABBR",
|
||||||
"ACRONYM",
|
"ACRONYM",
|
||||||
"B",
|
"B",
|
||||||
@@ -106,7 +106,7 @@ export class Translator {
|
|||||||
"SCRIPT",
|
"SCRIPT",
|
||||||
"SELECT",
|
"SELECT",
|
||||||
"SMALL",
|
"SMALL",
|
||||||
"SPAN",
|
// "SPAN",
|
||||||
"STRONG",
|
"STRONG",
|
||||||
"SUB",
|
"SUB",
|
||||||
"SUP",
|
"SUP",
|
||||||
@@ -206,6 +206,8 @@ export class Translator {
|
|||||||
|
|
||||||
// 14. 包含常见扩展名的文件名 (例如: document.pdf, image.jpeg)
|
// 14. 包含常见扩展名的文件名 (例如: document.pdf, image.jpeg)
|
||||||
/^[^\s\\/:]+?\.[a-zA-Z0-9]{2,5}$/,
|
/^[^\s\\/:]+?\.[a-zA-Z0-9]{2,5}$/,
|
||||||
|
|
||||||
|
// todo: 数字和特殊字符组成的字符串
|
||||||
];
|
];
|
||||||
|
|
||||||
static DEFAULT_OPTIONS = DEFAULT_SETTING; // 默认配置
|
static DEFAULT_OPTIONS = DEFAULT_SETTING; // 默认配置
|
||||||
@@ -221,6 +223,7 @@ export class Translator {
|
|||||||
|
|
||||||
if (Translator.TAGS.INLINE.has(el.nodeName)) return false;
|
if (Translator.TAGS.INLINE.has(el.nodeName)) return false;
|
||||||
if (Translator.TAGS.BLOCK.has(el.nodeName)) return true;
|
if (Translator.TAGS.BLOCK.has(el.nodeName)) return true;
|
||||||
|
if (el.attributes?.display?.value?.includes("inline")) return false;
|
||||||
|
|
||||||
if (Translator.displayCache.has(el)) {
|
if (Translator.displayCache.has(el)) {
|
||||||
return Translator.displayCache.get(el);
|
return Translator.displayCache.get(el);
|
||||||
@@ -231,11 +234,22 @@ export class Translator {
|
|||||||
return isBlock;
|
return isBlock;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 判断是否包含块级子元素
|
||||||
|
static hasBlockNode(el) {
|
||||||
|
if (!Translator.isElementOrFragment(el)) return false;
|
||||||
|
for (const child of el.childNodes) {
|
||||||
|
if (Translator.isBlockNode(child)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// 判断是否直接包含非空文本节点
|
// 判断是否直接包含非空文本节点
|
||||||
static hasTextNode(el) {
|
static hasTextNode(el) {
|
||||||
if (!Translator.isElementOrFragment(el)) return false;
|
if (!Translator.isElementOrFragment(el)) return false;
|
||||||
for (const node of el.childNodes) {
|
for (const child of el.childNodes) {
|
||||||
if (node.nodeType === Node.TEXT_NODE && /\S/.test(node.nodeValue)) {
|
if (child.nodeType === Node.TEXT_NODE && /\S/.test(child.nodeValue)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -248,11 +262,11 @@ export class Translator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 内置忽略元素
|
// 内置忽略元素
|
||||||
static BUILTIN_IGNORE_SELECTOR = `abbr, address, area, audio, br, canvas, code,
|
static BUILTIN_IGNORE_SELECTOR = `address, area, audio, br, canvas,
|
||||||
data, datalist, dfn, embed, head, iframe, img, input, kbd, noscript, map,
|
data, datalist, embed, head, iframe, input, noscript, map,
|
||||||
object, option, output, param, picture, progress,
|
object, option, param, picture, progress,
|
||||||
samp, select, script, style, sub, sup, svg, track, time, textarea, template,
|
select, script, style, track, textarea, template,
|
||||||
var, video, wbr, .notranslate, [contenteditable], [translate='no'],
|
video, wbr, .notranslate, [contenteditable], [translate='no'],
|
||||||
${APP_LCNAME}, #${APP_CONSTS.fabID}, #${APP_CONSTS.boxID},
|
${APP_LCNAME}, #${APP_CONSTS.fabID}, #${APP_CONSTS.boxID},
|
||||||
.${APP_CONSTS.fabID}_warpper, .${APP_CONSTS.boxID}_warpper`;
|
.${APP_CONSTS.fabID}_warpper, .${APP_CONSTS.boxID}_warpper`;
|
||||||
|
|
||||||
@@ -528,33 +542,35 @@ export class Translator {
|
|||||||
#createMutationObserver() {
|
#createMutationObserver() {
|
||||||
return new MutationObserver((mutations) => {
|
return new MutationObserver((mutations) => {
|
||||||
for (const mutation of mutations) {
|
for (const mutation of mutations) {
|
||||||
if (this.#skipMoNodes.has(mutation.target)) return;
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
mutation.type === "characterData" &&
|
this.#skipMoNodes.has(mutation.target) ||
|
||||||
mutation.oldValue !== mutation.target.nodeValue
|
mutation.nextSibling?.tagName === this.#translationTagName
|
||||||
) {
|
) {
|
||||||
this.#queueForRescan(mutation.target.parentElement);
|
continue;
|
||||||
} else if (mutation.type === "childList") {
|
}
|
||||||
if (mutation.nextSibling?.tagName === this.#translationTagName) {
|
|
||||||
// 恢复原文时插入元素,忽略
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (mutation.type === "characterData") {
|
||||||
|
if (
|
||||||
|
mutation.oldValue !== mutation.target.nodeValue &&
|
||||||
|
!this.#combinedSkipsRegex.test(mutation.target.nodeValue)
|
||||||
|
) {
|
||||||
|
this.#queueForRescan(mutation.target.parentElement);
|
||||||
|
}
|
||||||
|
} else if (mutation.type === "childList") {
|
||||||
let nodes = new Set();
|
let nodes = new Set();
|
||||||
let hasText = false;
|
let hasText = false;
|
||||||
mutation.addedNodes.forEach((node) => {
|
mutation.addedNodes.forEach((node) => {
|
||||||
if (this.#skipMoNodes.has(node)) return;
|
if (
|
||||||
|
this.#skipMoNodes.has(node) ||
|
||||||
|
node.nodeName === this.#translationTagName
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (/\S/.test(node.nodeValue)) {
|
if (node.nodeType === Node.TEXT_NODE) {
|
||||||
if (node.nodeType === Node.TEXT_NODE) {
|
hasText = true;
|
||||||
hasText = true;
|
} else if (Translator.isElementOrFragment(node)) {
|
||||||
} else if (
|
nodes.add(node);
|
||||||
Translator.isElementOrFragment(node) &&
|
|
||||||
node.nodeName !== this.#translationTagName
|
|
||||||
) {
|
|
||||||
nodes.add(node);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (hasText) {
|
if (hasText) {
|
||||||
@@ -772,6 +788,7 @@ export class Translator {
|
|||||||
#scanNode(rootNode) {
|
#scanNode(rootNode) {
|
||||||
if (
|
if (
|
||||||
!Translator.isElementOrFragment(rootNode) ||
|
!Translator.isElementOrFragment(rootNode) ||
|
||||||
|
// rootNode.matches?.(this.#rule.keepSelector) ||
|
||||||
rootNode.matches?.(this.#ignoreSelector)
|
rootNode.matches?.(this.#ignoreSelector)
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
@@ -783,13 +800,24 @@ export class Translator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const hasText = Translator.hasTextNode(rootNode);
|
const hasText = Translator.hasTextNode(rootNode);
|
||||||
if (hasText) {
|
|
||||||
|
if (!hasText && rootNode.children.length === 1) {
|
||||||
|
this.#scanNode(rootNode.children[0]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasBlock = Translator.hasBlockNode(rootNode);
|
||||||
|
|
||||||
|
if (hasText || !hasBlock) {
|
||||||
this.#startObserveNode(rootNode);
|
this.#startObserveNode(rootNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const child of rootNode.children) {
|
if (hasBlock) {
|
||||||
if (!hasText || Translator.isBlockNode(child)) {
|
for (const child of rootNode.children) {
|
||||||
this.#scanNode(child);
|
const isBlock = Translator.isBlockNode(child);
|
||||||
|
if (!hasText || isBlock) {
|
||||||
|
this.#scanNode(child);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1028,6 +1056,7 @@ export class Translator {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
Translator.TAGS.BREAK_LINE.has(node.nodeName) ||
|
Translator.TAGS.BREAK_LINE.has(node.nodeName) ||
|
||||||
|
node.matches?.(this.#ignoreSelector) ||
|
||||||
node.nodeName === this.#translationTagName
|
node.nodeName === this.#translationTagName
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
@@ -1240,10 +1269,7 @@ export class Translator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 文本节点
|
// 文本节点
|
||||||
if (
|
if (node.nodeType === Node.TEXT_NODE) {
|
||||||
this.#rule.hasRichText === "false" ||
|
|
||||||
node.nodeType === Node.TEXT_NODE
|
|
||||||
) {
|
|
||||||
let text = node.textContent;
|
let text = node.textContent;
|
||||||
|
|
||||||
// 专业术语替换
|
// 专业术语替换
|
||||||
@@ -1269,8 +1295,10 @@ export class Translator {
|
|||||||
// 元素节点
|
// 元素节点
|
||||||
if (node.nodeType === Node.ELEMENT_NODE) {
|
if (node.nodeType === Node.ELEMENT_NODE) {
|
||||||
if (
|
if (
|
||||||
Translator.TAGS.REPLACE.has(node.tagName) ||
|
(this.#rule.hasRichText === "true" &&
|
||||||
|
Translator.TAGS.REPLACE.has(node.tagName)) ||
|
||||||
node.matches(this.#rule.keepSelector) ||
|
node.matches(this.#rule.keepSelector) ||
|
||||||
|
node.matches(this.#ignoreSelector) ||
|
||||||
!node.textContent.trim()
|
!node.textContent.trim()
|
||||||
) {
|
) {
|
||||||
if (node.tagName === "IMG" || node.tagName === "SVG") {
|
if (node.tagName === "IMG" || node.tagName === "SVG") {
|
||||||
@@ -1285,7 +1313,10 @@ export class Translator {
|
|||||||
innerContent += traverse(child);
|
innerContent += traverse(child);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (Translator.TAGS.WARP.has(node.tagName)) {
|
if (
|
||||||
|
this.#rule.hasRichText === "true" &&
|
||||||
|
Translator.TAGS.WARP.has(node.tagName)
|
||||||
|
) {
|
||||||
wrapCounter++;
|
wrapCounter++;
|
||||||
const startPlaceholder = `<${this.#placeholder.tagName}${wrapCounter}>`;
|
const startPlaceholder = `<${this.#placeholder.tagName}${wrapCounter}>`;
|
||||||
const endPlaceholder = `</${this.#placeholder.tagName}${wrapCounter}>`;
|
const endPlaceholder = `</${this.#placeholder.tagName}${wrapCounter}>`;
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import { logger } from "./log";
|
|||||||
export default class TranslatorManager {
|
export default class TranslatorManager {
|
||||||
#clearShortcuts = [];
|
#clearShortcuts = [];
|
||||||
#menuCommandIds = [];
|
#menuCommandIds = [];
|
||||||
#clearTouchListener = null;
|
#clearTouchListeners = [];
|
||||||
#isActive = false;
|
#isActive = false;
|
||||||
#isUserscript;
|
#isUserscript;
|
||||||
#isIframe;
|
#isIframe;
|
||||||
@@ -110,10 +110,8 @@ export default class TranslatorManager {
|
|||||||
this.#clearShortcuts = [];
|
this.#clearShortcuts = [];
|
||||||
|
|
||||||
// 触屏
|
// 触屏
|
||||||
if (this.#clearTouchListener) {
|
this.#clearTouchListeners.forEach((clear) => clear());
|
||||||
this.#clearTouchListener();
|
this.#clearTouchListeners = [];
|
||||||
this.#clearTouchListener = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 油猴菜单
|
// 油猴菜单
|
||||||
if (globalThis.GM && this.#menuCommandIds.length > 0) {
|
if (globalThis.GM && this.#menuCommandIds.length > 0) {
|
||||||
@@ -145,8 +143,8 @@ export default class TranslatorManager {
|
|||||||
#setupTouchOperations() {
|
#setupTouchOperations() {
|
||||||
if (this.#isIframe) return;
|
if (this.#isIframe) return;
|
||||||
|
|
||||||
const { touchTranslate = 2 } = this._translator.setting;
|
const { touchModes = [2] } = this._translator.setting;
|
||||||
if (touchTranslate === 0) {
|
if (touchModes.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,35 +152,31 @@ export default class TranslatorManager {
|
|||||||
this.#processActions({ action: MSG_TRANS_TOGGLE });
|
this.#processActions({ action: MSG_TRANS_TOGGLE });
|
||||||
};
|
};
|
||||||
|
|
||||||
switch (touchTranslate) {
|
const handleListener = (mode) => {
|
||||||
case 2:
|
let options = null;
|
||||||
case 3:
|
switch (mode) {
|
||||||
case 4:
|
case 2:
|
||||||
this.#clearTouchListener = touchTapListener(handleTap, {
|
case 3:
|
||||||
taps: 1,
|
case 4:
|
||||||
fingers: touchTranslate,
|
options = { taps: 1, fingers: mode };
|
||||||
});
|
break;
|
||||||
break;
|
case 5:
|
||||||
case 5:
|
options = { taps: 2, fingers: 1 };
|
||||||
this.#clearTouchListener = touchTapListener(handleTap, {
|
break;
|
||||||
taps: 2,
|
case 6:
|
||||||
fingers: 1,
|
options = { taps: 3, fingers: 1 };
|
||||||
});
|
break;
|
||||||
break;
|
case 7:
|
||||||
case 6:
|
options = { taps: 2, fingers: 2 };
|
||||||
this.#clearTouchListener = touchTapListener(handleTap, {
|
break;
|
||||||
taps: 3,
|
default:
|
||||||
fingers: 1,
|
}
|
||||||
});
|
if (options) {
|
||||||
break;
|
this.#clearTouchListeners.push(touchTapListener(handleTap, options));
|
||||||
case 7:
|
}
|
||||||
this.#clearTouchListener = touchTapListener(handleTap, {
|
};
|
||||||
taps: 2,
|
|
||||||
fingers: 2,
|
touchModes.forEach((mode) => handleListener(mode));
|
||||||
});
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#handleWindowMessage(event) {
|
#handleWindowMessage(event) {
|
||||||
|
|||||||
@@ -590,7 +590,7 @@ class YouTubeCaptionProvider {
|
|||||||
return subtitles;
|
return subtitles;
|
||||||
}
|
}
|
||||||
|
|
||||||
#isQualityPoor(lines, lengthThreshold = 250, percentageThreshold = 0.1) {
|
#isQualityPoor(lines, lengthThreshold = 250, percentageThreshold = 0.2) {
|
||||||
if (lines.length === 0) return false;
|
if (lines.length === 0) return false;
|
||||||
const longLinesCount = lines.filter(
|
const longLinesCount = lines.filter(
|
||||||
(line) => line.text.length > lengthThreshold
|
(line) => line.text.length > lengthThreshold
|
||||||
@@ -913,7 +913,7 @@ class YouTubeCaptionProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#showNotification(message, duration = 3000) {
|
#showNotification(message, duration = 2000) {
|
||||||
if (!this.#notificationEl) this.#createNotificationElement();
|
if (!this.#notificationEl) this.#createNotificationElement();
|
||||||
this.#notificationEl.textContent = message;
|
this.#notificationEl.textContent = message;
|
||||||
this.#notificationEl.style.opacity = "1";
|
this.#notificationEl.style.opacity = "1";
|
||||||
|
|||||||
@@ -459,6 +459,7 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
|
|||||||
type="number"
|
type="number"
|
||||||
name="splitLength"
|
name="splitLength"
|
||||||
value={splitLength}
|
value={splitLength}
|
||||||
|
disabled={disabled}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
min={0}
|
min={0}
|
||||||
max={1000}
|
max={1000}
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ export default function Settings() {
|
|||||||
newlineLength = TRANS_NEWLINE_LENGTH,
|
newlineLength = TRANS_NEWLINE_LENGTH,
|
||||||
httpTimeout = DEFAULT_HTTP_TIMEOUT,
|
httpTimeout = DEFAULT_HTTP_TIMEOUT,
|
||||||
contextMenuType = 1,
|
contextMenuType = 1,
|
||||||
touchTranslate = 2,
|
touchModes = [2],
|
||||||
blacklist = DEFAULT_BLACKLIST.join(",\n"),
|
blacklist = DEFAULT_BLACKLIST.join(",\n"),
|
||||||
csplist = DEFAULT_CSPLIST.join(",\n"),
|
csplist = DEFAULT_CSPLIST.join(",\n"),
|
||||||
orilist = DEFAULT_ORILIST.join(",\n"),
|
orilist = DEFAULT_ORILIST.join(",\n"),
|
||||||
@@ -268,10 +268,13 @@ export default function Settings() {
|
|||||||
select
|
select
|
||||||
fullWidth
|
fullWidth
|
||||||
size="small"
|
size="small"
|
||||||
name="touchTranslate"
|
name="touchModes"
|
||||||
value={touchTranslate}
|
value={touchModes}
|
||||||
label={i18n("touch_translate_shortcut")}
|
label={i18n("touch_translate_shortcut")}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
|
SelectProps={{
|
||||||
|
multiple: true,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{[0, 2, 3, 4, 5, 6, 7].map((item) => (
|
{[0, 2, 3, 4, 5, 6, 7].map((item) => (
|
||||||
<MenuItem key={item} value={item}>
|
<MenuItem key={item} value={item}>
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ export default function Tranbox() {
|
|||||||
hideClickAway = false,
|
hideClickAway = false,
|
||||||
simpleStyle = false,
|
simpleStyle = false,
|
||||||
followSelection = false,
|
followSelection = false,
|
||||||
|
autoHeight = false,
|
||||||
triggerMode = OPT_TRANBOX_TRIGGER_CLICK,
|
triggerMode = OPT_TRANBOX_TRIGGER_CLICK,
|
||||||
// extStyles = "",
|
// extStyles = "",
|
||||||
enDict = OPT_DICT_BING,
|
enDict = OPT_DICT_BING,
|
||||||
@@ -330,6 +331,20 @@ export default function Tranbox() {
|
|||||||
max={200}
|
max={200}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
<Grid item xs={12} sm={12} md={6} lg={3}>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
select
|
||||||
|
size="small"
|
||||||
|
name="autoHeight"
|
||||||
|
value={autoHeight}
|
||||||
|
label={i18n("tranbox_auto_height")}
|
||||||
|
onChange={handleChange}
|
||||||
|
>
|
||||||
|
<MenuItem value={false}>{i18n("disable")}</MenuItem>
|
||||||
|
<MenuItem value={true}>{i18n("enable")}</MenuItem>
|
||||||
|
</TextField>
|
||||||
|
</Grid>
|
||||||
{!isExt && (
|
{!isExt && (
|
||||||
<Grid item xs={12} sm={12} md={6} lg={3}>
|
<Grid item xs={12} sm={12} md={6} lg={3}>
|
||||||
<ShortcutInput
|
<ShortcutInput
|
||||||
|
|||||||
@@ -150,6 +150,7 @@ export default function DraggableResizable({
|
|||||||
setPosition,
|
setPosition,
|
||||||
onChangeSize,
|
onChangeSize,
|
||||||
onChangePosition,
|
onChangePosition,
|
||||||
|
autoHeight,
|
||||||
...props
|
...props
|
||||||
}) {
|
}) {
|
||||||
const lineWidth = 4;
|
const lineWidth = 4;
|
||||||
@@ -222,11 +223,19 @@ export default function DraggableResizable({
|
|||||||
</Pointer>
|
</Pointer>
|
||||||
<Box
|
<Box
|
||||||
className="KT-draggable-container"
|
className="KT-draggable-container"
|
||||||
style={{
|
style={
|
||||||
width: size.w,
|
autoHeight
|
||||||
height: size.h,
|
? {
|
||||||
overflow: "hidden auto",
|
width: size.w,
|
||||||
}}
|
maxHeight: size.h,
|
||||||
|
overflow: "hidden auto",
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
width: size.w,
|
||||||
|
height: size.h,
|
||||||
|
overflow: "hidden auto",
|
||||||
|
}
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -115,7 +115,15 @@ export default function TranBox({
|
|||||||
text,
|
text,
|
||||||
setText,
|
setText,
|
||||||
setShowBox,
|
setShowBox,
|
||||||
tranboxSetting: { enDict, enSug, apiSlugs, fromLang, toLang, toLang2 },
|
tranboxSetting: {
|
||||||
|
enDict,
|
||||||
|
enSug,
|
||||||
|
apiSlugs,
|
||||||
|
fromLang,
|
||||||
|
toLang,
|
||||||
|
toLang2,
|
||||||
|
autoHeight,
|
||||||
|
},
|
||||||
transApis,
|
transApis,
|
||||||
boxSize,
|
boxSize,
|
||||||
setBoxSize,
|
setBoxSize,
|
||||||
@@ -141,6 +149,7 @@ export default function TranBox({
|
|||||||
size={boxSize}
|
size={boxSize}
|
||||||
setSize={setBoxSize}
|
setSize={setBoxSize}
|
||||||
setPosition={setBoxPosition}
|
setPosition={setBoxPosition}
|
||||||
|
autoHeight={autoHeight}
|
||||||
header={
|
header={
|
||||||
<Header
|
<Header
|
||||||
setShowBox={setShowBox}
|
setShowBox={setShowBox}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
import { isMobile } from "../../libs/mobile";
|
import { isMobile } from "../../libs/mobile";
|
||||||
import { kissLog } from "../../libs/log";
|
import { kissLog } from "../../libs/log";
|
||||||
import { useLangMap } from "../../hooks/I18n";
|
import { useLangMap } from "../../hooks/I18n";
|
||||||
|
import { debouncePutTranBox, getTranBox } from "../../libs/storage";
|
||||||
|
|
||||||
export default function Slection({
|
export default function Slection({
|
||||||
contextMenuType,
|
contextMenuType,
|
||||||
@@ -107,6 +108,29 @@ export default function Slection({
|
|||||||
return "onMouseUp";
|
return "onMouseUp";
|
||||||
}, [triggerMode]);
|
}, [triggerMode]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const { w, h, x, y } = (await getTranBox()) || {};
|
||||||
|
if (w !== undefined && h !== undefined) {
|
||||||
|
setBoxSize({ w, h });
|
||||||
|
}
|
||||||
|
if (x !== undefined && y !== undefined) {
|
||||||
|
setBoxPosition({
|
||||||
|
x: limitNumber(x, 0, window.innerWidth),
|
||||||
|
y: limitNumber(y, 0, window.innerHeight),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
debouncePutTranBox({ ...boxSize, ...boxPosition });
|
||||||
|
}, [boxSize, boxPosition]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function handleMouseup(e) {
|
async function handleMouseup(e) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|||||||
Reference in New Issue
Block a user