feat: Restructured core logic to support automatic page scanning and rich text translation

This commit is contained in:
Gabe
2025-09-21 19:51:57 +08:00
parent 7dc847fca2
commit 943a9e86f0
24 changed files with 2095 additions and 705 deletions

View File

@@ -6,6 +6,7 @@
"private": true,
"dependencies": {
"@emotion/cache": "^11.11.0",
"@emotion/css": "^11.13.5",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@mui/icons-material": "^5.15.15",

83
pnpm-lock.yaml generated
View File

@@ -11,6 +11,9 @@ importers:
'@emotion/cache':
specifier: ^11.11.0
version: 11.11.0
'@emotion/css':
specifier: ^11.13.5
version: 11.13.5
'@emotion/react':
specifier: ^11.11.1
version: 11.11.1(@types/react@18.2.79)(react@18.2.0)
@@ -963,18 +966,33 @@ packages:
'@emotion/babel-plugin@11.11.0':
resolution: {integrity: sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==}
'@emotion/babel-plugin@11.13.5':
resolution: {integrity: sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==}
'@emotion/cache@11.11.0':
resolution: {integrity: sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==}
'@emotion/cache@11.14.0':
resolution: {integrity: sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==}
'@emotion/css@11.13.5':
resolution: {integrity: sha512-wQdD0Xhkn3Qy2VNcIzbLP9MR8TafI0MJb7BEAXKp+w4+XqErksWR4OXomuDzPsN4InLdGhVe6EYcn2ZIUCpB8w==}
'@emotion/hash@0.9.1':
resolution: {integrity: sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==}
'@emotion/hash@0.9.2':
resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==}
'@emotion/is-prop-valid@1.2.1':
resolution: {integrity: sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==}
'@emotion/memoize@0.8.1':
resolution: {integrity: sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==}
'@emotion/memoize@0.9.0':
resolution: {integrity: sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==}
'@emotion/react@11.11.1':
resolution: {integrity: sha512-5mlW1DquU5HaxjLkfkGN1GA/fvVGdyHURRiX/0FHl2cfIfRxSOfmxEH5YS43edp0OldZrZ+dkBKbngxcNCdZvA==}
peerDependencies:
@@ -987,9 +1005,15 @@ packages:
'@emotion/serialize@1.1.2':
resolution: {integrity: sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA==}
'@emotion/serialize@1.3.3':
resolution: {integrity: sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==}
'@emotion/sheet@1.2.2':
resolution: {integrity: sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==}
'@emotion/sheet@1.4.0':
resolution: {integrity: sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==}
'@emotion/styled@11.11.0':
resolution: {integrity: sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng==}
peerDependencies:
@@ -1000,6 +1024,9 @@ packages:
'@types/react':
optional: true
'@emotion/unitless@0.10.0':
resolution: {integrity: sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==}
'@emotion/unitless@0.8.1':
resolution: {integrity: sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==}
@@ -1011,9 +1038,15 @@ packages:
'@emotion/utils@1.2.1':
resolution: {integrity: sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==}
'@emotion/utils@1.4.2':
resolution: {integrity: sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==}
'@emotion/weak-memoize@0.3.1':
resolution: {integrity: sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==}
'@emotion/weak-memoize@0.4.0':
resolution: {integrity: sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==}
'@eslint-community/eslint-utils@4.4.0':
resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -7012,6 +7045,20 @@ snapshots:
source-map: 0.5.7
stylis: 4.2.0
'@emotion/babel-plugin@11.13.5':
dependencies:
'@babel/helper-module-imports': 7.24.3
'@babel/runtime': 7.24.4
'@emotion/hash': 0.9.2
'@emotion/memoize': 0.9.0
'@emotion/serialize': 1.3.3
babel-plugin-macros: 3.1.0
convert-source-map: 1.9.0
escape-string-regexp: 4.0.0
find-root: 1.1.0
source-map: 0.5.7
stylis: 4.2.0
'@emotion/cache@11.11.0':
dependencies:
'@emotion/memoize': 0.8.1
@@ -7020,14 +7067,34 @@ snapshots:
'@emotion/weak-memoize': 0.3.1
stylis: 4.2.0
'@emotion/cache@11.14.0':
dependencies:
'@emotion/memoize': 0.9.0
'@emotion/sheet': 1.4.0
'@emotion/utils': 1.4.2
'@emotion/weak-memoize': 0.4.0
stylis: 4.2.0
'@emotion/css@11.13.5':
dependencies:
'@emotion/babel-plugin': 11.13.5
'@emotion/cache': 11.14.0
'@emotion/serialize': 1.3.3
'@emotion/sheet': 1.4.0
'@emotion/utils': 1.4.2
'@emotion/hash@0.9.1': {}
'@emotion/hash@0.9.2': {}
'@emotion/is-prop-valid@1.2.1':
dependencies:
'@emotion/memoize': 0.8.1
'@emotion/memoize@0.8.1': {}
'@emotion/memoize@0.9.0': {}
'@emotion/react@11.11.1(@types/react@18.2.79)(react@18.2.0)':
dependencies:
'@babel/runtime': 7.22.15
@@ -7050,8 +7117,18 @@ snapshots:
'@emotion/utils': 1.2.1
csstype: 3.1.2
'@emotion/serialize@1.3.3':
dependencies:
'@emotion/hash': 0.9.2
'@emotion/memoize': 0.9.0
'@emotion/unitless': 0.10.0
'@emotion/utils': 1.4.2
csstype: 3.1.3
'@emotion/sheet@1.2.2': {}
'@emotion/sheet@1.4.0': {}
'@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.2.79)(react@18.2.0))(@types/react@18.2.79)(react@18.2.0)':
dependencies:
'@babel/runtime': 7.22.15
@@ -7065,6 +7142,8 @@ snapshots:
optionalDependencies:
'@types/react': 18.2.79
'@emotion/unitless@0.10.0': {}
'@emotion/unitless@0.8.1': {}
'@emotion/use-insertion-effect-with-fallbacks@1.0.1(react@18.2.0)':
@@ -7073,8 +7152,12 @@ snapshots:
'@emotion/utils@1.2.1': {}
'@emotion/utils@1.4.2': {}
'@emotion/weak-memoize@0.3.1': {}
'@emotion/weak-memoize@0.4.0': {}
'@eslint-community/eslint-utils@4.4.0(eslint@8.57.0)':
dependencies:
eslint: 8.57.0

View File

@@ -812,7 +812,7 @@ export const parseTransRes = (
case OPT_TRANS_TENCENT:
return res?.auto_translation?.map((text) => [text, res?.src_lang]);
case OPT_TRANS_VOLCENGINE:
return new Map([[0, [res?.translation, res?.detected_language]]]);
return [[res?.translation, res?.detected_language]];
case OPT_TRANS_OPENAI:
case OPT_TRANS_OPENAI_2:
case OPT_TRANS_OPENAI_3:

View File

@@ -9,7 +9,7 @@ import {
MSG_TRANS_GETRULE,
MSG_TRANS_PUTRULE,
MSG_OPEN_TRANBOX,
APP_LCNAME,
APP_CONSTS,
DEFAULT_TRANBOX_SETTING,
} from "./config";
import { getFabWithDefault, getSettingWithDefault } from "./libs/storage";
@@ -106,7 +106,7 @@ function runIframe(translator) {
async function showFab(translator) {
const fab = await getFabWithDefault();
const $action = document.createElement("div");
$action.setAttribute("id", APP_LCNAME);
$action.setAttribute("id", APP_CONSTS.fabID);
$action.style.fontSize = "0";
$action.style.width = "0";
$action.style.height = "0";
@@ -114,10 +114,11 @@ async function showFab(translator) {
const shadowContainer = $action.attachShadow({ mode: "closed" });
const emotionRoot = document.createElement("style");
const shadowRootElement = document.createElement("div");
shadowRootElement.classList.add(`${APP_CONSTS.fabID}_warpper`);
shadowContainer.appendChild(emotionRoot);
shadowContainer.appendChild(shadowRootElement);
const cache = createCache({
key: APP_LCNAME,
key: APP_CONSTS.fabID,
prepend: true,
container: emotionRoot,
});
@@ -151,7 +152,7 @@ function showTransbox(
}
const $tranbox = document.createElement("div");
$tranbox.setAttribute("id", "kiss-transbox");
$tranbox.setAttribute("id", APP_CONSTS.boxID);
$tranbox.style.fontSize = "0";
$tranbox.style.width = "0";
$tranbox.style.height = "0";
@@ -159,12 +160,14 @@ function showTransbox(
const shadowContainer = $tranbox.attachShadow({ mode: "closed" });
const emotionRoot = document.createElement("style");
const shadowRootElement = document.createElement("div");
shadowRootElement.classList.add(`KT-transbox`);
shadowRootElement.classList.add(`KT-transbox_${darkMode ? "dark" : "light"}`);
shadowRootElement.classList.add(`${APP_CONSTS.boxID}_warpper`);
shadowRootElement.classList.add(
`${APP_CONSTS.boxID}_${darkMode ? "dark" : "light"}`
);
shadowContainer.appendChild(emotionRoot);
shadowContainer.appendChild(shadowRootElement);
const cache = createCache({
key: "kiss-transbox",
key: APP_CONSTS.boxID,
prepend: true,
container: emotionRoot,
});

View File

@@ -2,6 +2,10 @@ export const APP_NAME = process.env.REACT_APP_NAME.trim()
.split(/\s+/)
.join("-");
export const APP_LCNAME = APP_NAME.toLowerCase();
export const APP_CONSTS = {
fabID: `${APP_LCNAME}-fab`,
boxID: `${APP_LCNAME}-box`,
};
export const THEME_LIGHT = "light";
export const THEME_DARK = "dark";

View File

@@ -243,9 +243,9 @@ export const I18N = {
zh_TW: `每次請求間隔時間 (0-5000ms)`,
},
translate_interval: {
zh: `重新翻译间隔时间 (100-5000ms)`,
en: `Retranslation Interval (100-5000ms)`,
zh_TW: `重新翻譯間隔時間 (100-5000ms)`,
zh: `翻译间隔时间 (10-2000ms)`,
en: `Translation Interval (10-2000ms)`,
zh_TW: `翻譯間隔時間 (10-2000ms)`,
},
http_timeout: {
zh: `请求超时时间 (5000-60000ms)`,
@@ -543,9 +543,9 @@ export const I18N = {
zh_TW: `1. 支援星號 (*) 萬用字元。2. 多個 URL 請以換行或英文逗號「,」分隔。`,
},
selector_helper: {
zh: `1、遵循CSS选择器语法。2、多个CSS选择器之间用“;”隔开。3、“shadow root”选择器和内部选择器用“>>>”隔开`,
en: `1. Follow CSS selector syntax. 2. Separate multiple CSS selectors with ";". 3. The "shadow root" selector and the internal selector are separated by ">>>".`,
zh_TW: `1. 遵循 CSS 選擇器語法。2. 多個 CSS 選擇器以「;」分隔。3.「shadow root」與內部選擇器以「>>>」分隔。`,
zh: `1、需要翻译的目标元素。2、开启自动扫描页面后本设置无效。3、遵循CSS选择器语法。`,
en: `1. The target element to be translated. 2. This setting is invalid when automatic page scanning is enabled. 3. Follow the CSS selector syntax.`,
zh_TW: `1、需要翻譯的目標元素。 2.開啟自動掃描頁面後,本設定無效。 3.遵循CSS選擇器語法。`,
},
translate_switch: {
zh: `开启翻译`,
@@ -573,9 +573,29 @@ export const I18N = {
zh_TW: `保留元素選擇器`,
},
keep_selector_helper: {
zh: `1、遵循CSS选择器语法。`,
en: `1. Follow CSS selector syntax.`,
zh_TW: `1. 遵循 CSS 選擇器語法。`,
zh: `1、目标元素下面需要原样保留的子节点。2、遵循CSS选择器语法。`,
en: `1. The child nodes under the target element need to remain intact. 2. Follow the CSS selector syntax.`,
zh_TW: `1. 目標元素下的子節點需要保持原樣。 2. 遵循 CSS 選擇器語法。`,
},
root_selector: {
zh: `根节点选择器`,
en: `Root node selector`,
zh_TW: `根節點選擇器`,
},
root_selector_helper: {
zh: `1、用于缩小页面翻译范围。2、遵循CSS选择器语法。`,
en: `1. Used to narrow the translation scope of the page. 2. Follow the CSS selector syntax.`,
zh_TW: `1.用於縮小頁面翻譯範圍。 2、遵循CSS選擇器語法。`,
},
ignore_selector: {
zh: `不翻译节点选择器`,
en: `Ignore node selectors`,
zh_TW: `不翻譯節點選擇器`,
},
ignore_selector_helper: {
zh: `1、需要忽略的节点。2、遵循CSS选择器语法。`,
en: `1. Nodes to be ignored. 2. Follow CSS selector syntax.`,
zh_TW: `1、需要忽略的節點。 2、遵循CSS選擇器語法。`,
},
terms: {
zh: `专业术语`,
@@ -608,9 +628,9 @@ export const I18N = {
zh_TW: `注入 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.`,
zh_TW: `1. 開啟翻譯時注入並執行關閉翻譯時移除。2. 隨頁面變化,可能多次注入與執行`,
zh: `初始化时注入运行,一个页面仅运行一次`,
en: `Injected and run at initialization, and only run once per page.`,
zh_TW: `初始化時注入運行,一個頁面僅運行一次`,
},
inject_css: {
zh: `注入CSS`,
@@ -618,14 +638,9 @@ export const I18N = {
zh_TW: `注入 CSS`,
},
inject_css_helper: {
zh: `开启翻译时注入,关闭翻译时将移除`,
en: `Injected when translation is enabled and removed when translation is disabled.`,
zh_TW: `開啟翻譯時注入,關閉翻譯時會移除`,
},
root_selector: {
zh: `根选择器`,
en: `Root Selector`,
zh_TW: `根選擇器`,
zh: `初始化时注入运行,一个页面仅运行一次`,
en: `Injected and run at initialization, and only run once per page.`,
zh_TW: `初始化時注入運行,一個頁面僅運行一次`,
},
fixer_function: {
zh: `修复函数`,
@@ -1184,9 +1199,9 @@ export const I18N = {
zh_TW: `翻譯開始 Hook`,
},
translate_start_hook_helper: {
zh: `翻译开始时运行,入参为: 翻译节点,原文文本,返回:待译文本`,
en: `Run when translation starts, the input parameters are: translation node, original text, and returns: text to be translated.`,
zh_TW: `翻譯開始時執行,入參為:翻譯節點、原文文字,回傳:待譯文本`,
zh: `翻译时运行,入参为: 翻译节点列表`,
en: `Run before translation, input parameters are: translation node list.`,
zh_TW: `翻譯前時運行,入參為: 翻譯節點清單`,
},
translate_end_hook: {
zh: `翻译完成钩子函数`,
@@ -1194,9 +1209,9 @@ export const I18N = {
zh_TW: `翻譯完成 Hook`,
},
translate_end_hook_helper: {
zh: `翻译完成时运行,入参为: 翻译节点,译文文本,原文文本,保留元素、术语列表,返回:译文文本`,
en: `Run when the translation is completed, the input parameters are: translation node, translation text, original text, retained elements, and returns: translation text.`,
zh_TW: `翻譯完成時行,入參為:翻譯節點、譯文文字、原文文字、保留元素,返回:譯文文本`,
zh: `翻译完成时运行,入参为: 翻译节点列表`,
en: `Run when translation is complete, input parameters are: translation node list.`,
zh_TW: `翻譯完成時行,入參為: 翻譯節點清單`,
},
translate_remove_hook: {
zh: `翻译移除钩子函数`,
@@ -1258,4 +1273,59 @@ export const I18N = {
en: `Number of context sessions(1-20)`,
zh_TW: `上下文會話數量(1-20)`,
},
auto_scan_page: {
zh: `自动扫描页面`,
en: `Auto scan page`,
zh_TW: `自動掃描頁面`,
},
has_rich_text: {
zh: `启用富文本翻译`,
en: `Enable rich text translation`,
zh_TW: `啟用富文本翻譯`,
},
has_shadowroot: {
zh: `扫描Shadowroot`,
en: `Scan Shadowroot`,
zh_TW: `掃描Shadowroot`,
},
mousehover_translate: {
zh: `鼠标悬停翻译`,
en: `Mouseover Translation`,
zh_TW: `滑鼠懸停翻譯`,
},
use_mousehover_translation: {
zh: `启用鼠标悬停翻译`,
en: `Enable mouseover translation`,
zh_TW: `啟用滑鼠懸停翻譯`,
},
selected_translation_alert: {
zh: `划词翻译的开启和关闭请到“规则设置”里面设置。`,
en: `To turn selected translation on or off, please go to "Rule Settings".`,
zh_TW: `劃詞翻譯的開啟和關閉請到「規則設定」裡面設定。`,
},
mousehover_key_help: {
zh: `默认为“ControlLeft”`,
en: `Defaults is "ControlLeft"`,
zh_TW: `預設為“ControlLeft”`,
},
autoscan_alt: {
zh: `自动扫描`,
en: `Auto Scan`,
zh_TW: `自動掃描`,
},
shadowroot_alt: {
zh: `ShadowRoot`,
en: `ShadowRoot`,
zh_TW: `ShadowRoot`,
},
richtext_alt: {
zh: `富文本`,
en: `Rich Text`,
zh_TW: `富文本`,
},
transonly_alt: {
zh: `隐藏原文`,
en: `Hide Original`,
zh_TW: `隱藏原文`,
},
};

View File

@@ -58,7 +58,10 @@ export const OPT_TIMING_ALL = [
OPT_TIMING_ALT,
];
export const DEFAULT_SELECTOR = `:is(li, p, h1, h2, h3, h4, h5, h6, dd, blockquote, .kiss-p)`;
export const DEFAULT_SELECTOR =
"h1, h2, h3, h4, h5, h6, li, p, dd, blockquote, figcaption, label, legend";
export const DEFAULT_IGNORE_SELECTOR =
"button, code, footer, form, header, mark, nav, pre";
export const DEFAULT_KEEP_SELECTOR = `code, img, svg, pre`;
export const DEFAULT_RULE = {
pattern: "", // 匹配网址
@@ -77,17 +80,22 @@ export const DEFAULT_RULE = {
injectJs: "", // 注入JS
injectCss: "", // 注入CSS
transOnly: GLOBAL_KEY, // 是否仅显示译文
transTiming: GLOBAL_KEY, // 翻译时机/鼠标悬停翻译
// transTiming: GLOBAL_KEY, // 翻译时机/鼠标悬停翻译 (暂时作废)
transTag: GLOBAL_KEY, // 译文元素标签
transTitle: GLOBAL_KEY, // 是否同时翻译页面标题
transSelected: GLOBAL_KEY, // 是否启用划词翻译
detectRemote: GLOBAL_KEY, // 是否使用远程语言检测
skipLangs: [], // 不翻译的语言
fixerSelector: "", // 修复函数选择器
fixerFunc: GLOBAL_KEY, // 修复函数
// fixerSelector: "", // 修复函数选择器 (暂时作废)
// fixerFunc: GLOBAL_KEY, // 修复函数 (暂时作废)
transStartHook: "", // 钩子函数
transEndHook: "", // 钩子函数
transRemoveHook: "", // 钩子函数
// transRemoveHook: "", // 钩子函数 (暂时作废)
autoScan: GLOBAL_KEY, // 是否自动识别文本节点
hasRichText: GLOBAL_KEY, // 是否启用富文本翻译
hasShadowroot: GLOBAL_KEY, // 是否包含shadowroot
rootsSelector: "", // 翻译范围选择器
ignoreSelector: "", // 不翻译的选择器
};
// 全局规则
@@ -99,7 +107,7 @@ export const GLOBLA_RULE = {
translator: OPT_TRANS_MICROSOFT, // 翻译服务
fromLang: "auto", // 源语言
toLang: "zh-CN", // 目标语言
textStyle: OPT_STYLE_DASHLINE, // 译文样式
textStyle: OPT_STYLE_NONE, // 译文样式
transOpen: "false", // 开启翻译
bgColor: "", // 译文颜色
textDiyStyle: "", // 自定义译文样式
@@ -108,17 +116,22 @@ export const GLOBLA_RULE = {
injectJs: "", // 注入JS
injectCss: "", // 注入CSS
transOnly: "false", // 是否仅显示译文
transTiming: OPT_TIMING_PAGESCROLL, // 翻译时机/鼠标悬停翻译
// transTiming: OPT_TIMING_PAGESCROLL, // 翻译时机/鼠标悬停翻译 (暂时作废)
transTag: DEFAULT_TRANS_TAG, // 译文元素标签
transTitle: "false", // 是否同时翻译页面标题
transSelected: "true", // 是否启用划词翻译
detectRemote: "false", // 是否使用远程语言检测
skipLangs: [], // 不翻译的语言
fixerSelector: "", // 修复函数选择器
fixerFunc: "-", // 修复函数
// fixerSelector: "", // 修复函数选择器 (暂时作废)
// fixerFunc: "-", // 修复函数 (暂时作废)
transStartHook: "", // 钩子函数
transEndHook: "", // 钩子函数
transRemoveHook: "", // 钩子函数
// transRemoveHook: "", // 钩子函数 (暂时作废)
autoScan: "true", // 是否自动识别文本节点
hasRichText: "true", // 是否启用富文本翻译
hasShadowroot: "false", // 是否包含shadowroot
rootsSelector: "body", // 翻译范围选择器
ignoreSelector: DEFAULT_IGNORE_SELECTOR, // 不翻译的选择器
};
export const DEFAULT_RULES = [GLOBLA_RULE];

View File

@@ -18,8 +18,8 @@ export const DEFAULT_SHORTCUTS = {
[OPT_SHORTCUT_SETTING]: ["AltLeft", "KeyO"],
};
export const TRANS_MIN_LENGTH = 5; // 最短翻译长度
export const TRANS_MAX_LENGTH = 10000; // 最长翻译长度
export const TRANS_MIN_LENGTH = 2; // 最短翻译长度
export const TRANS_MAX_LENGTH = 100000; // 最长翻译长度
export const TRANS_NEWLINE_LENGTH = 20; // 换行字符数
export const DEFAULT_BLACKLIST = [
"https://fishjar.github.io/kiss-translator/options.html",
@@ -108,6 +108,12 @@ export const DEFAULT_SUBRULES_LIST = [
},
];
export const DEFAULT__MOUSEHOVER_KEY = ["ControlLeft"];
export const DEFAULT_MOUSE_HOVER_SETTING = {
useMouseHover: true, // 是否启用鼠标悬停翻译
mouseHoverKey: DEFAULT__MOUSEHOVER_KEY, // 鼠标悬停翻译组合键
};
export const DEFAULT_SETTING = {
darkMode: false, // 深色模式
uiLang: "en", // 界面语言
@@ -137,6 +143,7 @@ export const DEFAULT_SETTING = {
blacklist: DEFAULT_BLACKLIST.join(",\n"), // 禁用翻译名单
csplist: DEFAULT_CSPLIST.join(",\n"), // 禁用CSP名单
// disableLangs: [], // 不翻译的语言(移至rule作废)
transInterval: 500, // 翻译间隔时间
transInterval: 200, // 翻译等待时间
langDetector: OPT_TRANS_MICROSOFT, // 远程语言识别服务
mouseHoverSetting: DEFAULT_MOUSE_HOVER_SETTING, // 鼠标悬停翻译
};

19
src/hooks/MouseHover.js Normal file
View File

@@ -0,0 +1,19 @@
import { useCallback } from "react";
import { DEFAULT_MOUSE_HOVER_SETTING } from "../config";
import { useSetting } from "./Setting";
export function useMouseHoverSetting() {
const { setting, updateSetting } = useSetting();
const mouseHoverSetting =
setting?.mouseHoverSetting || DEFAULT_MOUSE_HOVER_SETTING;
const updateMouseHoverSetting = useCallback(
async (obj) => {
Object.assign(mouseHoverSetting, obj);
await updateSetting({ mouseHoverSetting });
},
[mouseHoverSetting, updateSetting]
);
return { mouseHoverSetting, updateMouseHoverSetting };
}

View File

@@ -1,7 +1,7 @@
// Function to inject inline JavaScript code
export const injectInlineJs = (code) => {
const el = document.createElement("script");
el.setAttribute("data-source", "KISS-Calendar injectInlineJs");
el.setAttribute("data-source", "kiss-inject injectInlineJs");
el.setAttribute("type", "text/javascript");
el.textContent = code;
document.body?.appendChild(el);
@@ -10,7 +10,7 @@ export const injectInlineJs = (code) => {
// Function to inject external JavaScript file
export const injectExternalJs = (src) => {
const el = document.createElement("script");
el.setAttribute("data-source", "KISS-Calendar injectExternalJs");
el.setAttribute("data-source", "kiss-inject injectExternalJs");
el.setAttribute("type", "text/javascript");
el.setAttribute("src", src);
document.body?.appendChild(el);
@@ -19,7 +19,7 @@ export const injectExternalJs = (src) => {
// Function to inject internal CSS code
export const injectInternalCss = (styles) => {
const el = document.createElement("style");
el.setAttribute("data-source", "KISS-Calendar injectInternalCss");
el.setAttribute("data-source", "kiss-inject injectInternalCss");
el.textContent = styles;
document.head?.appendChild(el);
};
@@ -27,7 +27,7 @@ export const injectInternalCss = (styles) => {
// Function to inject external CSS file
export const injectExternalCss = (href) => {
const el = document.createElement("link");
el.setAttribute("data-source", "KISS-Calendar injectExternalCss");
el.setAttribute("data-source", "kiss-inject injectExternalCss");
el.setAttribute("rel", "stylesheet");
el.setAttribute("type", "text/css");
el.setAttribute("href", href);

View File

@@ -6,13 +6,13 @@ import {
OPT_STYLE_ALL,
OPT_LANGS_FROM,
OPT_LANGS_TO,
OPT_TIMING_ALL,
// OPT_TIMING_ALL,
GLOBLA_RULE,
} from "../config";
import { loadOrFetchSubRules } from "./subRules";
import { getRulesWithDefault, setRules } from "./storage";
import { trySyncRules } from "./sync";
import { FIXER_ALL } from "./webfix";
// import { FIXER_ALL } from "./webfix";
import { kissLog } from "./log";
/**
@@ -68,15 +68,17 @@ export const matchRule = async (
[
"selector",
"keepSelector",
"rootsSelector",
"ignoreSelector",
"terms",
"selectStyle",
"parentStyle",
"injectJs",
"injectCss",
"fixerSelector",
// "fixerSelector",
"transStartHook",
"transEndHook",
"transRemoveHook",
// "transRemoveHook",
].forEach((key) => {
if (!rule[key]?.trim()) {
rule[key] = globalRule[key];
@@ -89,12 +91,15 @@ export const matchRule = async (
"toLang",
"transOpen",
"transOnly",
"transTiming",
// "transTiming",
"autoScan",
"hasRichText",
"hasShadowroot",
"transTag",
"transTitle",
"transSelected",
"detectRemote",
"fixerFunc",
// "fixerFunc",
].forEach((key) => {
if (rule[key] === undefined || rule[key] === GLOBAL_KEY) {
rule[key] = globalRule[key];
@@ -146,6 +151,8 @@ export const checkRules = (rules) => {
pattern,
selector,
keepSelector,
rootsSelector,
ignoreSelector,
terms,
selectStyle,
parentStyle,
@@ -159,21 +166,26 @@ export const checkRules = (rules) => {
bgColor,
textDiyStyle,
transOnly,
transTiming,
autoScan,
hasRichText,
hasShadowroot,
// transTiming,
transTag,
transTitle,
transSelected,
detectRemote,
skipLangs,
fixerSelector,
fixerFunc,
// fixerSelector,
// fixerFunc,
transStartHook,
transEndHook,
transRemoveHook,
// transRemoveHook,
}) => ({
pattern: pattern.trim(),
selector: type(selector) === "string" ? selector : "",
keepSelector: type(keepSelector) === "string" ? keepSelector : "",
rootsSelector: type(rootsSelector) === "string" ? rootsSelector : "",
ignoreSelector: type(ignoreSelector) === "string" ? ignoreSelector : "",
terms: type(terms) === "string" ? terms : "",
selectStyle: type(selectStyle) === "string" ? selectStyle : "",
parentStyle: type(parentStyle) === "string" ? parentStyle : "",
@@ -187,18 +199,21 @@ export const checkRules = (rules) => {
textStyle: matchValue([GLOBAL_KEY, ...OPT_STYLE_ALL], textStyle),
transOpen: matchValue([GLOBAL_KEY, "true", "false"], transOpen),
transOnly: matchValue([GLOBAL_KEY, "true", "false"], transOnly),
transTiming: matchValue([GLOBAL_KEY, ...OPT_TIMING_ALL], transTiming),
autoScan: matchValue([GLOBAL_KEY, "true", "false"], autoScan),
hasRichText: matchValue([GLOBAL_KEY, "true", "false"], hasRichText),
hasShadowroot: matchValue([GLOBAL_KEY, "true", "false"], hasShadowroot),
// transTiming: matchValue([GLOBAL_KEY, ...OPT_TIMING_ALL], transTiming),
transTag: matchValue([GLOBAL_KEY, "span", "font"], transTag),
transTitle: matchValue([GLOBAL_KEY, "true", "false"], transTitle),
transSelected: matchValue([GLOBAL_KEY, "true", "false"], transSelected),
detectRemote: matchValue([GLOBAL_KEY, "true", "false"], detectRemote),
skipLangs: type(skipLangs) === "array" ? skipLangs : [],
fixerSelector: type(fixerSelector) === "string" ? fixerSelector : "",
// fixerSelector: type(fixerSelector) === "string" ? fixerSelector : "",
transStartHook: type(transStartHook) === "string" ? transStartHook : "",
transEndHook: type(transEndHook) === "string" ? transEndHook : "",
transRemoveHook:
type(transRemoveHook) === "string" ? transRemoveHook : "",
fixerFunc: matchValue([GLOBAL_KEY, ...FIXER_ALL], fixerFunc),
// transRemoveHook:
// type(transRemoveHook) === "string" ? transRemoveHook : "",
// fixerFunc: matchValue([GLOBAL_KEY, ...FIXER_ALL], fixerFunc),
})
);

56
src/libs/shadowroot.js Normal file
View File

@@ -0,0 +1,56 @@
import { kissLog } from "./log";
/**
* @class ShadowRootMonitor
* @description 通过覆写 Element.prototype.attachShadow 来监控页面上所有新创建的 Shadow DOM
*/
export class ShadowRootMonitor {
/**
* @param {function(ShadowRoot): void} callback - 当一个新的 shadowRoot 被创建时调用的回调函数。
*/
constructor(callback) {
if (typeof callback !== "function") {
throw new Error("Callback must be a function.");
}
this.callback = callback;
this.isMonitoring = false;
this.originalAttachShadow = Element.prototype.attachShadow;
}
/**
* 开始监控 shadowRoot 的创建。
*/
start() {
if (this.isMonitoring) {
return;
}
const monitorInstance = this;
Element.prototype.attachShadow = function (...args) {
const shadowRoot = monitorInstance.originalAttachShadow.apply(this, args);
if (shadowRoot) {
try {
monitorInstance.callback(shadowRoot);
} catch (error) {
kissLog(error, "Error in ShadowRootMonitor callback");
}
}
return shadowRoot;
};
this.isMonitoring = true;
}
/**
* 停止监控,并恢复原始的 attachShadow 方法。
*/
stop() {
if (!this.isMonitoring) {
return;
}
Element.prototype.attachShadow = this.originalAttachShadow;
this.isMonitoring = false;
}
}

102
src/libs/style.js Normal file
View File

@@ -0,0 +1,102 @@
import { css } from "@emotion/css";
import {
OPT_STYLE_NONE,
OPT_STYLE_LINE,
OPT_STYLE_DOTLINE,
OPT_STYLE_DASHLINE,
OPT_STYLE_WAVYLINE,
OPT_STYLE_DASHBOX,
OPT_STYLE_FUZZY,
OPT_STYLE_HIGHLIGHT,
OPT_STYLE_BLOCKQUOTE,
OPT_STYLE_DIY,
DEFAULT_COLOR,
} from "../config";
const genLineStyle = (style, color) => `
opacity: 0.6;
-webkit-opacity: 0.6;
text-decoration-line: underline;
text-decoration-style: ${style};
text-decoration-color: ${color};
text-decoration-thickness: 2px;
text-underline-offset: 0.3em;
-webkit-text-decoration-line: underline;
-webkit-text-decoration-style: ${style};
-webkit-text-decoration-color: ${color};
-webkit-text-decoration-thickness: 2px;
-webkit-text-underline-offset: 0.3em;
&:hover {
opacity: 1;
-webkit-opacity: 1;
}
`;
const genStyles = ({ textDiyStyle, bgColor = DEFAULT_COLOR }) => ({
// 无样式
[OPT_STYLE_NONE]: ``,
// 下划线
[OPT_STYLE_LINE]: genLineStyle("solid", bgColor),
// 点状线
[OPT_STYLE_DOTLINE]: genLineStyle("dotted", bgColor),
// 虚线
[OPT_STYLE_DASHLINE]: genLineStyle("dashed", bgColor),
// 波浪线
[OPT_STYLE_WAVYLINE]: genLineStyle("wavy", bgColor),
// 虚线框
[OPT_STYLE_DASHBOX]: `
color: ${bgColor || DEFAULT_COLOR};
border: 1px dashed ${bgColor || DEFAULT_COLOR};
background: transparent;
display: block;
padding: 0.2em 0.5em;
box-sizing: border-box;
`,
// 模糊
[OPT_STYLE_FUZZY]: `
filter: blur(0.2em);
-webkit-filter: blur(0.2em);
&:hover {
filter: none;
-webkit-filter: none;
}
`,
// 高亮
[OPT_STYLE_HIGHLIGHT]: `
color: #fff;
background-color: ${bgColor || DEFAULT_COLOR};
`,
// 引用
[OPT_STYLE_BLOCKQUOTE]: `
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;
}
`,
// 自定义
[OPT_STYLE_DIY]: textDiyStyle,
});
export const genTextClass = ({ textDiyStyle, bgColor = DEFAULT_COLOR }) => {
const styles = genStyles({ textDiyStyle, bgColor });
const textClass = {};
let textStyles = "";
Object.entries(styles).forEach(([k, v]) => {
textClass[k] = css`
${v}
`;
});
Object.entries(styles).forEach(([k, v]) => {
textStyles += `
.${textClass[k]} {
${v}
}
`;
});
return [textClass, textStyles];
};

View File

@@ -1,34 +1,14 @@
export const loadingSvg = `
<svg viewBox="0 0 100 100" style="display:inline-block; width:100%; height: 100%; max-width: 24; max-height: 24;">
<circle fill="#209CEE" stroke="none" cx="6" cy="50" r="6">
<animateTransform
attributeName="transform"
dur="1s"
type="translate"
values="0 15 ; 0 -15; 0 15"
repeatCount="indefinite"
begin="0.1"
/>
</circle>
<circle fill="#209CEE" stroke="none" cx="30" cy="50" r="6">
<animateTransform
attributeName="transform"
dur="1s"
type="translate"
values="0 10 ; 0 -10; 0 10"
repeatCount="indefinite"
begin="0.2"
/>
</circle>
<circle fill="#209CEE" stroke="none" cx="54" cy="50" r="6">
<animateTransform
attributeName="transform"
dur="1s"
type="translate"
values="0 5 ; 0 -5; 0 5"
repeatCount="indefinite"
begin="0.3"
/>
</circle>
<svg viewBox="0 0 100 100"
style="display: inline-block; width: 1em; height: 1em; vertical-align: middle;">
<circle fill="#209CEE" stroke="none" cx="6" cy="50" r="6">
<animateTransform attributeName="transform" dur="1s" type="translate" values="0 15 ; 0 -15; 0 15" repeatCount="indefinite" begin="0.1"/>
</circle>
<circle fill="#209CEE" stroke="none" cx="30" cy="50" r="6">
<animateTransform attributeName="transform" dur="1s" type="translate" values="0 10 ; 0 -10; 0 10" repeatCount="indefinite" begin="0.2"/>
</circle>
<circle fill="#209CEE" stroke="none" cx="54" cy="50" r="6">
<animateTransform attributeName="transform" dur="1s" type="translate" values="0 5 ; 0 -5; 0 5" repeatCount="indefinite" begin="0.3"/>
</circle>
</svg>
`;

File diff suppressed because it is too large Load Diff

View File

@@ -177,7 +177,7 @@ export const sha256 = async (text, salt) => {
* 生成随机事件名称
* @returns
*/
export const genEventName = () => btoa(Math.random()).slice(3, 11);
export const genEventName = () => `kiss-${btoa(Math.random()).slice(3, 11)}`;
/**
* 判断两个 Set 是否相同
@@ -302,3 +302,16 @@ export const extractJson = (raw) => {
const match = s.match(/\{[\s\S]*\}/);
return match ? match[0] : "{}";
};
/**
* 空闲执行
* @param {*} cb
* @param {*} timeout
* @returns
*/
export const scheduleIdle = (cb, timeout = 200) => {
if (window.requestIdleCallback) {
return requestIdleCallback(cb, { timeout });
}
return setTimeout(cb, timeout);
};

View File

@@ -560,7 +560,12 @@ function ApiAccordion({ translator }) {
return (
<Accordion expanded={expanded} onChange={handleChange}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Typography>
<Typography
sx={{
opacity: api.isDisabled ? 0.5 : 1,
overflowWrap: "anywhere",
}}
>
{api.apiName ? `${translator} (${api.apiName})` : translator}
</Typography>
</AccordionSummary>

View File

@@ -0,0 +1,57 @@
import Box from "@mui/material/Box";
import Stack from "@mui/material/Stack";
import { useI18n } from "../../hooks/I18n";
import ShortcutInput from "./ShortcutInput";
import FormControlLabel from "@mui/material/FormControlLabel";
import Switch from "@mui/material/Switch";
import { useMouseHoverSetting } from "../../hooks/MouseHover";
import { useCallback } from "react";
import Grid from "@mui/material/Grid";
export default function MouseHoverSetting() {
const i18n = useI18n();
const { mouseHoverSetting, updateMouseHoverSetting } = useMouseHoverSetting();
const handleShortcutInput = useCallback(
(val) => {
updateMouseHoverSetting({ mouseHoverKey: val });
},
[updateMouseHoverSetting]
);
const { useMouseHover = true, mouseHoverKey = ["ControlLeft"] } =
mouseHoverSetting;
return (
<Box>
<Stack spacing={3}>
<FormControlLabel
control={
<Switch
size="small"
name="useMouseHover"
checked={useMouseHover}
onChange={() => {
updateMouseHoverSetting({ useMouseHover: !useMouseHover });
}}
/>
}
label={i18n("use_mousehover_translation")}
/>
<Box>
<Grid container spacing={2} columns={12}>
<Grid item xs={12} sm={12} md={4} lg={4}>
<ShortcutInput
value={mouseHoverKey}
onChange={handleShortcutInput}
label={i18n("trigger_trans_shortcut")}
helperText={i18n("mousehover_key_help")}
/>
</Grid>
</Grid>
</Box>
</Stack>
</Box>
);
}

View File

@@ -14,6 +14,7 @@ import ApiIcon from "@mui/icons-material/Api";
import InputIcon from "@mui/icons-material/Input";
import SelectAllIcon from "@mui/icons-material/SelectAll";
import EventNoteIcon from "@mui/icons-material/EventNote";
import MouseIcon from '@mui/icons-material/Mouse';
function LinkItem({ label, url, icon }) {
const match = useMatch(url);
@@ -52,6 +53,12 @@ export default function Navigator(props) {
url: "/tranbox",
icon: <SelectAllIcon />,
},
{
id: "mousehover_translate",
label: i18n("mousehover_translate"),
url: "/mousehover",
icon: <MouseIcon />,
},
{
id: "apis_setting",
label: i18n("apis_setting"),

View File

@@ -16,9 +16,7 @@ import {
OPT_STYLE_USE_COLOR,
URL_KISS_RULES_NEW_ISSUE,
OPT_SYNCTYPE_WORKER,
OPT_TIMING_PAGESCROLL,
DEFAULT_TRANS_TAG,
OPT_TIMING_ALL,
} from "../../config";
import { useState, useEffect, useMemo } from "react";
import { useI18n } from "../../hooks/I18n";
@@ -55,7 +53,6 @@ import HelpButton from "./HelpButton";
import { useSyncCaches } from "../../hooks/Sync";
import DownloadButton from "./DownloadButton";
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";
@@ -78,6 +75,8 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
pattern,
selector,
keepSelector = "",
rootsSelector = "",
ignoreSelector = "",
terms = "",
selectStyle = "",
parentStyle = "",
@@ -91,17 +90,20 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
bgColor,
textDiyStyle,
transOnly = "false",
transTiming = OPT_TIMING_PAGESCROLL,
autoScan = "true",
hasRichText = "true",
hasShadowroot = "false",
// transTiming = OPT_TIMING_PAGESCROLL,
transTag = DEFAULT_TRANS_TAG,
transTitle = "false",
transSelected = "true",
detectRemote = "false",
skipLangs = [],
fixerSelector = "",
fixerFunc = "-",
// fixerSelector = "",
// fixerFunc = "-",
transStartHook = "",
transEndHook = "",
transRemoveHook = "",
// transRemoveHook = "",
} = formValues;
const hasSamePattern = (str) => {
@@ -236,7 +238,7 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
helperText={errors.selector || i18n("selector_helper")}
name="selector"
value={selector}
disabled={disabled}
disabled={autoScan === "true" || disabled}
onChange={handleChange}
onFocus={handleFocus}
multiline
@@ -251,6 +253,26 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
onChange={handleChange}
multiline
/>
<TextField
size="small"
label={i18n("root_selector")}
helperText={i18n("root_selector_helper")}
name="rootsSelector"
value={rootsSelector}
disabled={disabled}
onChange={handleChange}
multiline
/>
<TextField
size="small"
label={i18n("ignore_selector")}
helperText={i18n("ignore_selector_helper")}
name="ignoreSelector"
value={ignoreSelector}
disabled={disabled}
onChange={handleChange}
multiline
/>
<Box>
<Grid container spacing={2} columns={12}>
@@ -270,6 +292,126 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
<MenuItem value={"false"}>{i18n("default_disabled")}</MenuItem>
</TextField>
</Grid>
<Grid item xs={12} sm={6} md={3} lg={2}>
<TextField
select
size="small"
fullWidth
name="autoScan"
value={autoScan}
label={i18n("auto_scan_page")}
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="hasRichText"
value={hasRichText}
label={i18n("has_rich_text")}
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="hasShadowroot"
value={hasShadowroot}
label={i18n("has_shadowroot")}
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="transSelected"
value={transSelected}
label={i18n("translate_selected")}
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="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>
</Box>
<Box>
<Grid container spacing={2} columns={12}>
{/* <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="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
@@ -378,41 +520,6 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
<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
@@ -429,38 +536,6 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
<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="transSelected"
value={transSelected}
label={i18n("translate_selected")}
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
@@ -482,7 +557,7 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
{showMore && (
<>
<TextField
{/* <TextField
size="small"
label={i18n("fixer_selector")}
name="fixerSelector"
@@ -508,7 +583,7 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
{item}
</MenuItem>
))}
</TextField>
</TextField> */}
<TextField
select
@@ -542,40 +617,6 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
maxRows={10}
/>
<TextField
size="small"
label={i18n("translate_start_hook")}
helperText={i18n("translate_start_hook_helper")}
name="transStartHook"
value={transStartHook}
disabled={disabled}
onChange={handleChange}
multiline
maxRows={10}
/>
<TextField
size="small"
label={i18n("translate_end_hook")}
helperText={i18n("translate_end_hook_helper")}
name="transEndHook"
value={transEndHook}
disabled={disabled}
onChange={handleChange}
multiline
maxRows={10}
/>
<TextField
size="small"
label={i18n("translate_remove_hook")}
helperText={i18n("translate_remove_hook_helper")}
name="transRemoveHook"
value={transRemoveHook}
disabled={disabled}
onChange={handleChange}
multiline
maxRows={10}
/>
<TextField
size="small"
label={i18n("selector_style")}
@@ -598,6 +639,41 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
maxRows={10}
multiline
/>
<TextField
size="small"
label={i18n("translate_start_hook")}
helperText={i18n("translate_start_hook_helper")}
name="transStartHook"
value={transStartHook}
disabled={disabled}
onChange={handleChange}
multiline
maxRows={10}
/>
<TextField
size="small"
label={i18n("translate_end_hook")}
helperText={i18n("translate_end_hook_helper")}
name="transEndHook"
value={transEndHook}
disabled={disabled}
onChange={handleChange}
multiline
maxRows={10}
/>
{/* <TextField
size="small"
label={i18n("translate_remove_hook")}
helperText={i18n("translate_remove_hook_helper")}
name="transRemoveHook"
value={transRemoveHook}
disabled={disabled}
onChange={handleChange}
multiline
maxRows={10}
/> */}
<TextField
size="small"
label={i18n("inject_css")}

View File

@@ -61,7 +61,7 @@ export default function Settings() {
value = limitNumber(value, 0, 5000);
break;
case "transInterval":
value = limitNumber(value, 100, 5000);
value = limitNumber(value, 10, 2000);
break;
case "minLength":
value = limitNumber(value, 1, 100);
@@ -119,7 +119,7 @@ export default function Settings() {
touchTranslate = 2,
blacklist = DEFAULT_BLACKLIST.join(",\n"),
csplist = DEFAULT_CSPLIST.join(",\n"),
transInterval = 500,
transInterval = 200,
langDetector = OPT_TRANS_MICROSOFT,
} = setting;
const { isHide = false } = fab || {};

View File

@@ -16,6 +16,7 @@ import { useCallback } from "react";
import { limitNumber } from "../../libs/utils";
import { useTranbox } from "../../hooks/Tranbox";
import { isExt } from "../../libs/client";
import Alert from "@mui/material/Alert";
export default function Tranbox() {
const i18n = useI18n();
@@ -67,6 +68,7 @@ export default function Tranbox() {
return (
<Box>
<Stack spacing={3}>
<Alert severity="info">{i18n("selected_translation_alert")}</Alert>
<TextField
select
size="small"

View File

@@ -21,6 +21,7 @@ import Apis from "./Apis";
import InputSetting from "./InputSetting";
import Tranbox from "./Tranbox";
import FavWords from "./FavWords";
import MouseHoverSetting from "./MouseHover";
export default function Options() {
const [error, setError] = useState("");
@@ -113,6 +114,7 @@ export default function Options() {
<Route path="rules" element={<Rules />} />
<Route path="input" element={<InputSetting />} />
<Route path="tranbox" element={<Tranbox />} />
<Route path="mousehover" element={<MouseHoverSetting />} />
<Route path="apis" element={<Apis />} />
<Route path="sync" element={<SyncSetting />} />
<Route path="words" element={<FavWords />} />

View File

@@ -5,6 +5,7 @@ import MenuItem from "@mui/material/MenuItem";
import FormControlLabel from "@mui/material/FormControlLabel";
import Switch from "@mui/material/Switch";
import Button from "@mui/material/Button";
import Grid from "@mui/material/Grid";
import { sendBgMsg, sendTabMsg, getCurTab } from "../../libs/msg";
import { browser } from "../../libs/browser";
import { isExt } from "../../libs/client";
@@ -30,6 +31,8 @@ import { saveRule } from "../../libs/rules";
import { tryClearCaches } from "../../libs";
import { kissLog } from "../../libs/log";
// 插件popup没有参数
// 网页弹框有
export default function Popup({ setShowPopup, translator: tran }) {
const i18n = useI18n();
const [rule, setRule] = useState(tran?.rule);
@@ -173,10 +176,20 @@ export default function Popup({ setShowPopup, translator: tran }) {
);
}
const { transOpen, translator, fromLang, toLang, textStyle } = rule;
const {
transOpen,
translator,
fromLang,
toLang,
textStyle,
autoScan,
transOnly,
hasRichText,
hasShadowroot,
} = rule;
return (
<Box minWidth={300}>
<Box width={320}>
{!tran && (
<>
<Header />
@@ -184,26 +197,79 @@ export default function Popup({ setShowPopup, translator: tran }) {
</>
)}
<Stack sx={{ p: 2 }} spacing={2}>
<Stack
direction="row"
justifyContent="space-between"
alignItems="center"
spacing={2}
>
<FormControlLabel
control={
<Switch
checked={transOpen === "true"}
onChange={handleTransToggle}
/>
}
label={
commands["toggleTranslate"]
? `${i18n("translate_alt")}(${commands["toggleTranslate"]})`
: i18n("translate_alt")
}
/>
</Stack>
<Grid container columns={12} spacing={1}>
<Grid item xs={12}>
<FormControlLabel
control={
<Switch
checked={transOpen === "true"}
onChange={handleTransToggle}
/>
}
label={
commands["toggleTranslate"]
? `${i18n("translate_alt")}(${commands["toggleTranslate"]})`
: i18n("translate_alt")
}
/>
</Grid>
<Grid item xs={6}>
<FormControlLabel
control={
<Switch
size="small"
name="autoScan"
value={autoScan === "true" ? "false" : "true"}
checked={autoScan === "true"}
onChange={handleChange}
/>
}
label={i18n("autoscan_alt")}
/>
</Grid>
<Grid item xs={6}>
<FormControlLabel
control={
<Switch
size="small"
name="hasShadowroot"
value={hasShadowroot === "true" ? "false" : "true"}
checked={hasShadowroot === "true"}
onChange={handleChange}
/>
}
label={i18n("shadowroot_alt")}
/>
</Grid>
<Grid item xs={6}>
<FormControlLabel
control={
<Switch
size="small"
name="transOnly"
value={transOnly === "true" ? "false" : "true"}
checked={transOnly === "true"}
onChange={handleChange}
/>
}
label={i18n("transonly_alt")}
/>
</Grid>
<Grid item xs={6}>
<FormControlLabel
control={
<Switch
size="small"
name="hasRichText"
value={hasRichText === "true" ? "false" : "true"}
checked={hasRichText === "true"}
onChange={handleChange}
/>
}
label={i18n("richtext_alt")}
/>
</Grid>
</Grid>
<TextField
select