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_CN=简约翻译
|
||||
REACT_APP_VERSION=2.0.3
|
||||
REACT_APP_VERSION=2.0.4
|
||||
|
||||
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)
|
||||
|
||||
### How to directly access the Tampermonkey script settings page
|
||||
|
||||
Settings page address: https://fishjar.github.io/kiss-translator/options.html
|
||||
|
||||
## Future Plans
|
||||
|
||||
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)
|
||||
|
||||
### 如何直接进入油猴脚本设置页面
|
||||
|
||||
设置页面地址: 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",
|
||||
"description": "A minimalist bilingual translation Extension & Greasemonkey Script",
|
||||
"version": "2.0.3",
|
||||
"version": "2.0.4",
|
||||
"author": "Gabe<yugang2002@gmail.com>",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"manifest_version": 2,
|
||||
"name": "__MSG_app_name__",
|
||||
"description": "__MSG_app_description__",
|
||||
"version": "2.0.3",
|
||||
"version": "2.0.4",
|
||||
"default_locale": "en",
|
||||
"author": "Gabe<yugang2002@gmail.com>",
|
||||
"homepage_url": "https://github.com/fishjar/kiss-translator",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"manifest_version": 3,
|
||||
"name": "__MSG_app_name__",
|
||||
"description": "__MSG_app_description__",
|
||||
"version": "2.0.3",
|
||||
"version": "2.0.4",
|
||||
"default_locale": "en",
|
||||
"author": "Gabe<yugang2002@gmail.com>",
|
||||
"homepage_url": "https://github.com/fishjar/kiss-translator",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"manifest_version": 2,
|
||||
"name": "__MSG_app_name__",
|
||||
"description": "__MSG_app_description__",
|
||||
"version": "2.0.3",
|
||||
"version": "2.0.4",
|
||||
"default_locale": "en",
|
||||
"author": "Gabe<yugang2002@gmail.com>",
|
||||
"homepage_url": "https://github.com/fishjar/kiss-translator",
|
||||
|
||||
@@ -847,7 +847,7 @@ export const parseTransRes = async (
|
||||
}
|
||||
return parseAIRes(modelMsg?.content);
|
||||
case OPT_TRANS_CUSTOMIZE:
|
||||
return res?.map((item) => [item.text, item.src]);
|
||||
return (res?.translations ?? res)?.map((item) => [item.text, item.src]);
|
||||
default:
|
||||
}
|
||||
|
||||
|
||||
@@ -1160,9 +1160,9 @@ export const I18N = {
|
||||
zh_TW: `觸控設定`,
|
||||
},
|
||||
touch_translate_shortcut: {
|
||||
zh: `触屏翻译快捷方式`,
|
||||
en: `Touch Translate Shortcut`,
|
||||
zh_TW: `觸控翻譯捷徑`,
|
||||
zh: `触屏翻译快捷方式 (支持多选)`,
|
||||
en: `Touch Translate Shortcut (multiple supported)`,
|
||||
zh_TW: `觸控翻譯捷徑 (支援多選)`,
|
||||
},
|
||||
touch_tap_0: {
|
||||
zh: `禁用`,
|
||||
@@ -1349,6 +1349,11 @@ export const I18N = {
|
||||
en: `Transbox Follow Selection`,
|
||||
zh_TW: `翻譯框跟隨選取文字`,
|
||||
},
|
||||
tranbox_auto_height: {
|
||||
zh: `翻译框自适应高度`,
|
||||
en: `Translation box adaptive height`,
|
||||
zh_TW: `翻譯框自適應高度`,
|
||||
},
|
||||
translate_start_hook: {
|
||||
zh: `翻译开始钩子函数`,
|
||||
en: `Translate Start Hook`,
|
||||
|
||||
@@ -97,7 +97,7 @@ background: linear-gradient(
|
||||
export const DEFAULT_SELECTOR =
|
||||
"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_KEEP_SELECTOR = `a:has(code)`;
|
||||
export const DEFAULT_KEEP_SELECTOR = `code, cite, math, .math, a:has(code)`;
|
||||
export const DEFAULT_RULE = {
|
||||
pattern: "", // 匹配网址
|
||||
selector: "", // 选择器
|
||||
@@ -211,7 +211,8 @@ const RULES_MAP = {
|
||||
},
|
||||
"twitter.com, https://x.com": {
|
||||
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`,
|
||||
},
|
||||
"www.youtube.com/live_chat": {
|
||||
|
||||
@@ -88,6 +88,7 @@ export const DEFAULT_TRANBOX_SETTING = {
|
||||
hideClickAway: false, // 是否点击外部关闭弹窗
|
||||
simpleStyle: false, // 是否简洁界面
|
||||
followSelection: false, // 翻译框是否跟随选中文本
|
||||
autoHeight: false, // 自适应高度
|
||||
triggerMode: OPT_TRANBOX_TRIGGER_CLICK, // 触发翻译方式
|
||||
// extStyles: "", // 附加样式
|
||||
enDict: OPT_DICT_BING, // 英文词典
|
||||
@@ -166,7 +167,8 @@ export const DEFAULT_SETTING = {
|
||||
shortcuts: DEFAULT_SHORTCUTS, // 快捷键
|
||||
inputRule: DEFAULT_INPUT_RULE, // 输入框设置
|
||||
tranboxSetting: DEFAULT_TRANBOX_SETTING, // 划词翻译设置
|
||||
touchTranslate: 2, // 触屏翻译 {5:单指双击,6:单指三击,7:双指双击}
|
||||
// touchTranslate: 2, // 触屏翻译 {5:单指双击,6:单指三击,7:双指双击} (作废)
|
||||
touchModes: [2], // 触屏翻译 {5:单指双击,6:单指三击,7:双指双击} (多选)
|
||||
blacklist: DEFAULT_BLACKLIST.join(",\n"), // 禁用翻译名单
|
||||
csplist: DEFAULT_CSPLIST.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_SYNC = `${APP_NAME}_sync`;
|
||||
export const STOKEY_FAB = `${APP_NAME}_fab`;
|
||||
export const STOKEY_TRANBOX = `${APP_NAME}_tranbox`;
|
||||
export const STOKEY_RULESCACHE_PREFIX = `${APP_NAME}_rulescache_`;
|
||||
|
||||
export const CACHE_NAME = `${APP_NAME}_cache`;
|
||||
|
||||
@@ -63,8 +63,8 @@ export const shortcutRegister = (targetKeys = [], fn, target = document) => {
|
||||
const targetKeySet = new Set(targetKeys);
|
||||
const onKeyDown = (pressedKeys, event) => {
|
||||
if (isSameSet(targetKeySet, pressedKeys)) {
|
||||
// event.preventDefault();
|
||||
event.stopPropagation();
|
||||
// event.preventDefault(); // 阻止浏览器的默认行为
|
||||
// event.stopPropagation(); // 阻止事件继续(向父元素)冒泡
|
||||
fn();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
STOKEY_RULES_OLD,
|
||||
STOKEY_WORDS,
|
||||
STOKEY_FAB,
|
||||
STOKEY_TRANBOX,
|
||||
STOKEY_SYNC,
|
||||
STOKEY_MSAUTH,
|
||||
STOKEY_BDAUTH,
|
||||
@@ -135,6 +136,13 @@ export const getFabWithDefault = async () => (await getFab()) || {};
|
||||
export const setFab = (obj) => setObj(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]: `
|
||||
border: 2px dashed ${bgColor || DEFAULT_COLOR};
|
||||
display: inline-block;
|
||||
display: block;
|
||||
padding: 0.2em 0.4em;
|
||||
box-sizing: border-box;
|
||||
`,
|
||||
|
||||
@@ -77,7 +77,7 @@ export class Translator {
|
||||
"VIDEO",
|
||||
]),
|
||||
INLINE: new Set([
|
||||
"A",
|
||||
// "A",
|
||||
"ABBR",
|
||||
"ACRONYM",
|
||||
"B",
|
||||
@@ -106,7 +106,7 @@ export class Translator {
|
||||
"SCRIPT",
|
||||
"SELECT",
|
||||
"SMALL",
|
||||
"SPAN",
|
||||
// "SPAN",
|
||||
"STRONG",
|
||||
"SUB",
|
||||
"SUP",
|
||||
@@ -206,6 +206,8 @@ export class Translator {
|
||||
|
||||
// 14. 包含常见扩展名的文件名 (例如: document.pdf, image.jpeg)
|
||||
/^[^\s\\/:]+?\.[a-zA-Z0-9]{2,5}$/,
|
||||
|
||||
// todo: 数字和特殊字符组成的字符串
|
||||
];
|
||||
|
||||
static DEFAULT_OPTIONS = DEFAULT_SETTING; // 默认配置
|
||||
@@ -221,6 +223,7 @@ export class Translator {
|
||||
|
||||
if (Translator.TAGS.INLINE.has(el.nodeName)) return false;
|
||||
if (Translator.TAGS.BLOCK.has(el.nodeName)) return true;
|
||||
if (el.attributes?.display?.value?.includes("inline")) return false;
|
||||
|
||||
if (Translator.displayCache.has(el)) {
|
||||
return Translator.displayCache.get(el);
|
||||
@@ -231,11 +234,22 @@ export class Translator {
|
||||
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) {
|
||||
if (!Translator.isElementOrFragment(el)) return false;
|
||||
for (const node of el.childNodes) {
|
||||
if (node.nodeType === Node.TEXT_NODE && /\S/.test(node.nodeValue)) {
|
||||
for (const child of el.childNodes) {
|
||||
if (child.nodeType === Node.TEXT_NODE && /\S/.test(child.nodeValue)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -248,11 +262,11 @@ export class Translator {
|
||||
}
|
||||
|
||||
// 内置忽略元素
|
||||
static BUILTIN_IGNORE_SELECTOR = `abbr, address, area, audio, br, canvas, code,
|
||||
data, datalist, dfn, embed, head, iframe, img, input, kbd, noscript, map,
|
||||
object, option, output, param, picture, progress,
|
||||
samp, select, script, style, sub, sup, svg, track, time, textarea, template,
|
||||
var, video, wbr, .notranslate, [contenteditable], [translate='no'],
|
||||
static BUILTIN_IGNORE_SELECTOR = `address, area, audio, br, canvas,
|
||||
data, datalist, embed, head, iframe, input, noscript, map,
|
||||
object, option, param, picture, progress,
|
||||
select, script, style, track, textarea, template,
|
||||
video, wbr, .notranslate, [contenteditable], [translate='no'],
|
||||
${APP_LCNAME}, #${APP_CONSTS.fabID}, #${APP_CONSTS.boxID},
|
||||
.${APP_CONSTS.fabID}_warpper, .${APP_CONSTS.boxID}_warpper`;
|
||||
|
||||
@@ -528,33 +542,35 @@ export class Translator {
|
||||
#createMutationObserver() {
|
||||
return new MutationObserver((mutations) => {
|
||||
for (const mutation of mutations) {
|
||||
if (this.#skipMoNodes.has(mutation.target)) return;
|
||||
|
||||
if (
|
||||
mutation.type === "characterData" &&
|
||||
mutation.oldValue !== mutation.target.nodeValue
|
||||
this.#skipMoNodes.has(mutation.target) ||
|
||||
mutation.nextSibling?.tagName === this.#translationTagName
|
||||
) {
|
||||
this.#queueForRescan(mutation.target.parentElement);
|
||||
} else if (mutation.type === "childList") {
|
||||
if (mutation.nextSibling?.tagName === this.#translationTagName) {
|
||||
// 恢复原文时插入元素,忽略
|
||||
continue;
|
||||
}
|
||||
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 hasText = false;
|
||||
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) {
|
||||
hasText = true;
|
||||
} else if (
|
||||
Translator.isElementOrFragment(node) &&
|
||||
node.nodeName !== this.#translationTagName
|
||||
) {
|
||||
nodes.add(node);
|
||||
}
|
||||
if (node.nodeType === Node.TEXT_NODE) {
|
||||
hasText = true;
|
||||
} else if (Translator.isElementOrFragment(node)) {
|
||||
nodes.add(node);
|
||||
}
|
||||
});
|
||||
if (hasText) {
|
||||
@@ -772,6 +788,7 @@ export class Translator {
|
||||
#scanNode(rootNode) {
|
||||
if (
|
||||
!Translator.isElementOrFragment(rootNode) ||
|
||||
// rootNode.matches?.(this.#rule.keepSelector) ||
|
||||
rootNode.matches?.(this.#ignoreSelector)
|
||||
) {
|
||||
return;
|
||||
@@ -783,13 +800,24 @@ export class Translator {
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
for (const child of rootNode.children) {
|
||||
if (!hasText || Translator.isBlockNode(child)) {
|
||||
this.#scanNode(child);
|
||||
if (hasBlock) {
|
||||
for (const child of rootNode.children) {
|
||||
const isBlock = Translator.isBlockNode(child);
|
||||
if (!hasText || isBlock) {
|
||||
this.#scanNode(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1028,6 +1056,7 @@ export class Translator {
|
||||
|
||||
if (
|
||||
Translator.TAGS.BREAK_LINE.has(node.nodeName) ||
|
||||
node.matches?.(this.#ignoreSelector) ||
|
||||
node.nodeName === this.#translationTagName
|
||||
) {
|
||||
return true;
|
||||
@@ -1240,10 +1269,7 @@ export class Translator {
|
||||
}
|
||||
|
||||
// 文本节点
|
||||
if (
|
||||
this.#rule.hasRichText === "false" ||
|
||||
node.nodeType === Node.TEXT_NODE
|
||||
) {
|
||||
if (node.nodeType === Node.TEXT_NODE) {
|
||||
let text = node.textContent;
|
||||
|
||||
// 专业术语替换
|
||||
@@ -1269,8 +1295,10 @@ export class Translator {
|
||||
// 元素节点
|
||||
if (node.nodeType === Node.ELEMENT_NODE) {
|
||||
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.#ignoreSelector) ||
|
||||
!node.textContent.trim()
|
||||
) {
|
||||
if (node.tagName === "IMG" || node.tagName === "SVG") {
|
||||
@@ -1285,7 +1313,10 @@ export class Translator {
|
||||
innerContent += traverse(child);
|
||||
});
|
||||
|
||||
if (Translator.TAGS.WARP.has(node.tagName)) {
|
||||
if (
|
||||
this.#rule.hasRichText === "true" &&
|
||||
Translator.TAGS.WARP.has(node.tagName)
|
||||
) {
|
||||
wrapCounter++;
|
||||
const startPlaceholder = `<${this.#placeholder.tagName}${wrapCounter}>`;
|
||||
const endPlaceholder = `</${this.#placeholder.tagName}${wrapCounter}>`;
|
||||
|
||||
@@ -28,7 +28,7 @@ import { logger } from "./log";
|
||||
export default class TranslatorManager {
|
||||
#clearShortcuts = [];
|
||||
#menuCommandIds = [];
|
||||
#clearTouchListener = null;
|
||||
#clearTouchListeners = [];
|
||||
#isActive = false;
|
||||
#isUserscript;
|
||||
#isIframe;
|
||||
@@ -110,10 +110,8 @@ export default class TranslatorManager {
|
||||
this.#clearShortcuts = [];
|
||||
|
||||
// 触屏
|
||||
if (this.#clearTouchListener) {
|
||||
this.#clearTouchListener();
|
||||
this.#clearTouchListener = null;
|
||||
}
|
||||
this.#clearTouchListeners.forEach((clear) => clear());
|
||||
this.#clearTouchListeners = [];
|
||||
|
||||
// 油猴菜单
|
||||
if (globalThis.GM && this.#menuCommandIds.length > 0) {
|
||||
@@ -145,8 +143,8 @@ export default class TranslatorManager {
|
||||
#setupTouchOperations() {
|
||||
if (this.#isIframe) return;
|
||||
|
||||
const { touchTranslate = 2 } = this._translator.setting;
|
||||
if (touchTranslate === 0) {
|
||||
const { touchModes = [2] } = this._translator.setting;
|
||||
if (touchModes.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -154,35 +152,31 @@ export default class TranslatorManager {
|
||||
this.#processActions({ action: MSG_TRANS_TOGGLE });
|
||||
};
|
||||
|
||||
switch (touchTranslate) {
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
this.#clearTouchListener = touchTapListener(handleTap, {
|
||||
taps: 1,
|
||||
fingers: touchTranslate,
|
||||
});
|
||||
break;
|
||||
case 5:
|
||||
this.#clearTouchListener = touchTapListener(handleTap, {
|
||||
taps: 2,
|
||||
fingers: 1,
|
||||
});
|
||||
break;
|
||||
case 6:
|
||||
this.#clearTouchListener = touchTapListener(handleTap, {
|
||||
taps: 3,
|
||||
fingers: 1,
|
||||
});
|
||||
break;
|
||||
case 7:
|
||||
this.#clearTouchListener = touchTapListener(handleTap, {
|
||||
taps: 2,
|
||||
fingers: 2,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
}
|
||||
const handleListener = (mode) => {
|
||||
let options = null;
|
||||
switch (mode) {
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
options = { taps: 1, fingers: mode };
|
||||
break;
|
||||
case 5:
|
||||
options = { taps: 2, fingers: 1 };
|
||||
break;
|
||||
case 6:
|
||||
options = { taps: 3, fingers: 1 };
|
||||
break;
|
||||
case 7:
|
||||
options = { taps: 2, fingers: 2 };
|
||||
break;
|
||||
default:
|
||||
}
|
||||
if (options) {
|
||||
this.#clearTouchListeners.push(touchTapListener(handleTap, options));
|
||||
}
|
||||
};
|
||||
|
||||
touchModes.forEach((mode) => handleListener(mode));
|
||||
}
|
||||
|
||||
#handleWindowMessage(event) {
|
||||
|
||||
@@ -590,7 +590,7 @@ class YouTubeCaptionProvider {
|
||||
return subtitles;
|
||||
}
|
||||
|
||||
#isQualityPoor(lines, lengthThreshold = 250, percentageThreshold = 0.1) {
|
||||
#isQualityPoor(lines, lengthThreshold = 250, percentageThreshold = 0.2) {
|
||||
if (lines.length === 0) return false;
|
||||
const longLinesCount = lines.filter(
|
||||
(line) => line.text.length > lengthThreshold
|
||||
@@ -913,7 +913,7 @@ class YouTubeCaptionProvider {
|
||||
}
|
||||
}
|
||||
|
||||
#showNotification(message, duration = 3000) {
|
||||
#showNotification(message, duration = 2000) {
|
||||
if (!this.#notificationEl) this.#createNotificationElement();
|
||||
this.#notificationEl.textContent = message;
|
||||
this.#notificationEl.style.opacity = "1";
|
||||
|
||||
@@ -459,6 +459,7 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
|
||||
type="number"
|
||||
name="splitLength"
|
||||
value={splitLength}
|
||||
disabled={disabled}
|
||||
onChange={handleChange}
|
||||
min={0}
|
||||
max={1000}
|
||||
|
||||
@@ -94,7 +94,7 @@ export default function Settings() {
|
||||
newlineLength = TRANS_NEWLINE_LENGTH,
|
||||
httpTimeout = DEFAULT_HTTP_TIMEOUT,
|
||||
contextMenuType = 1,
|
||||
touchTranslate = 2,
|
||||
touchModes = [2],
|
||||
blacklist = DEFAULT_BLACKLIST.join(",\n"),
|
||||
csplist = DEFAULT_CSPLIST.join(",\n"),
|
||||
orilist = DEFAULT_ORILIST.join(",\n"),
|
||||
@@ -268,10 +268,13 @@ export default function Settings() {
|
||||
select
|
||||
fullWidth
|
||||
size="small"
|
||||
name="touchTranslate"
|
||||
value={touchTranslate}
|
||||
name="touchModes"
|
||||
value={touchModes}
|
||||
label={i18n("touch_translate_shortcut")}
|
||||
onChange={handleChange}
|
||||
SelectProps={{
|
||||
multiple: true,
|
||||
}}
|
||||
>
|
||||
{[0, 2, 3, 4, 5, 6, 7].map((item) => (
|
||||
<MenuItem key={item} value={item}>
|
||||
|
||||
@@ -68,6 +68,7 @@ export default function Tranbox() {
|
||||
hideClickAway = false,
|
||||
simpleStyle = false,
|
||||
followSelection = false,
|
||||
autoHeight = false,
|
||||
triggerMode = OPT_TRANBOX_TRIGGER_CLICK,
|
||||
// extStyles = "",
|
||||
enDict = OPT_DICT_BING,
|
||||
@@ -330,6 +331,20 @@ export default function Tranbox() {
|
||||
max={200}
|
||||
/>
|
||||
</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 && (
|
||||
<Grid item xs={12} sm={12} md={6} lg={3}>
|
||||
<ShortcutInput
|
||||
|
||||
@@ -150,6 +150,7 @@ export default function DraggableResizable({
|
||||
setPosition,
|
||||
onChangeSize,
|
||||
onChangePosition,
|
||||
autoHeight,
|
||||
...props
|
||||
}) {
|
||||
const lineWidth = 4;
|
||||
@@ -222,11 +223,19 @@ export default function DraggableResizable({
|
||||
</Pointer>
|
||||
<Box
|
||||
className="KT-draggable-container"
|
||||
style={{
|
||||
width: size.w,
|
||||
height: size.h,
|
||||
overflow: "hidden auto",
|
||||
}}
|
||||
style={
|
||||
autoHeight
|
||||
? {
|
||||
width: size.w,
|
||||
maxHeight: size.h,
|
||||
overflow: "hidden auto",
|
||||
}
|
||||
: {
|
||||
width: size.w,
|
||||
height: size.h,
|
||||
overflow: "hidden auto",
|
||||
}
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
|
||||
@@ -115,7 +115,15 @@ export default function TranBox({
|
||||
text,
|
||||
setText,
|
||||
setShowBox,
|
||||
tranboxSetting: { enDict, enSug, apiSlugs, fromLang, toLang, toLang2 },
|
||||
tranboxSetting: {
|
||||
enDict,
|
||||
enSug,
|
||||
apiSlugs,
|
||||
fromLang,
|
||||
toLang,
|
||||
toLang2,
|
||||
autoHeight,
|
||||
},
|
||||
transApis,
|
||||
boxSize,
|
||||
setBoxSize,
|
||||
@@ -141,6 +149,7 @@ export default function TranBox({
|
||||
size={boxSize}
|
||||
setSize={setBoxSize}
|
||||
setPosition={setBoxPosition}
|
||||
autoHeight={autoHeight}
|
||||
header={
|
||||
<Header
|
||||
setShowBox={setShowBox}
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
import { isMobile } from "../../libs/mobile";
|
||||
import { kissLog } from "../../libs/log";
|
||||
import { useLangMap } from "../../hooks/I18n";
|
||||
import { debouncePutTranBox, getTranBox } from "../../libs/storage";
|
||||
|
||||
export default function Slection({
|
||||
contextMenuType,
|
||||
@@ -107,6 +108,29 @@ export default function Slection({
|
||||
return "onMouseUp";
|
||||
}, [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(() => {
|
||||
async function handleMouseup(e) {
|
||||
e.stopPropagation();
|
||||
|
||||
Reference in New Issue
Block a user