feat: Add more shortcut keys to popup
This commit is contained in:
@@ -6,24 +6,18 @@ import { CacheProvider } from "@emotion/react";
|
|||||||
import {
|
import {
|
||||||
MSG_TRANS_TOGGLE,
|
MSG_TRANS_TOGGLE,
|
||||||
MSG_TRANS_TOGGLE_STYLE,
|
MSG_TRANS_TOGGLE_STYLE,
|
||||||
MSG_TRANS_GETRULE,
|
|
||||||
MSG_TRANS_PUTRULE,
|
MSG_TRANS_PUTRULE,
|
||||||
MSG_OPEN_TRANBOX,
|
|
||||||
APP_CONSTS,
|
APP_CONSTS,
|
||||||
DEFAULT_TRANBOX_SETTING,
|
|
||||||
} from "./config";
|
} from "./config";
|
||||||
import { getFabWithDefault, getSettingWithDefault } from "./libs/storage";
|
import { getFabWithDefault, getSettingWithDefault } from "./libs/storage";
|
||||||
import { Translator } from "./libs/translator";
|
import { Translator } from "./libs/translator";
|
||||||
import { isIframe, sendIframeMsg } from "./libs/iframe";
|
import { isIframe, sendIframeMsg } from "./libs/iframe";
|
||||||
import Slection from "./views/Selection";
|
|
||||||
import { touchTapListener } from "./libs/touch";
|
import { touchTapListener } from "./libs/touch";
|
||||||
import { debounce, genEventName } from "./libs/utils";
|
import { debounce, genEventName } from "./libs/utils";
|
||||||
import { handlePing, injectScript } from "./libs/gm";
|
import { handlePing, injectScript } from "./libs/gm";
|
||||||
import { browser } from "./libs/browser";
|
|
||||||
import { matchRule } from "./libs/rules";
|
import { matchRule } from "./libs/rules";
|
||||||
import { trySyncAllSubRules } from "./libs/subRules";
|
import { trySyncAllSubRules } from "./libs/subRules";
|
||||||
import { isInBlacklist } from "./libs/blacklist";
|
import { isInBlacklist } from "./libs/blacklist";
|
||||||
import inputTranslate from "./libs/inputTranslate";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 油猴脚本设置页面
|
* 油猴脚本设置页面
|
||||||
@@ -45,37 +39,6 @@ function runSettingPage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 插件监听后端事件
|
|
||||||
* @param {*} translator
|
|
||||||
*/
|
|
||||||
function runtimeListener(translator) {
|
|
||||||
browser?.runtime.onMessage.addListener(async ({ action, args }) => {
|
|
||||||
switch (action) {
|
|
||||||
case MSG_TRANS_TOGGLE:
|
|
||||||
translator.toggle();
|
|
||||||
sendIframeMsg(MSG_TRANS_TOGGLE);
|
|
||||||
break;
|
|
||||||
case MSG_TRANS_TOGGLE_STYLE:
|
|
||||||
translator.toggleStyle();
|
|
||||||
sendIframeMsg(MSG_TRANS_TOGGLE_STYLE);
|
|
||||||
break;
|
|
||||||
case MSG_TRANS_GETRULE:
|
|
||||||
break;
|
|
||||||
case MSG_TRANS_PUTRULE:
|
|
||||||
translator.updateRule(args);
|
|
||||||
sendIframeMsg(MSG_TRANS_PUTRULE, args);
|
|
||||||
break;
|
|
||||||
case MSG_OPEN_TRANBOX:
|
|
||||||
window.dispatchEvent(new CustomEvent(MSG_OPEN_TRANBOX));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return { error: `message action is unavailable: ${action}` };
|
|
||||||
}
|
|
||||||
return { rule: translator.rule, setting: translator.setting };
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* iframe 页面执行
|
* iframe 页面执行
|
||||||
* @param {*} translator
|
* @param {*} translator
|
||||||
@@ -131,61 +94,6 @@ async function showFab(translator) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 划词翻译
|
|
||||||
* @param {*} param0
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
function showTransbox(
|
|
||||||
{
|
|
||||||
contextMenuType,
|
|
||||||
tranboxSetting = DEFAULT_TRANBOX_SETTING,
|
|
||||||
transApis,
|
|
||||||
darkMode,
|
|
||||||
uiLang,
|
|
||||||
langDetector,
|
|
||||||
},
|
|
||||||
{ transSelected }
|
|
||||||
) {
|
|
||||||
if (transSelected === "false") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const $tranbox = document.createElement("div");
|
|
||||||
$tranbox.setAttribute("id", APP_CONSTS.boxID);
|
|
||||||
$tranbox.style.fontSize = "0";
|
|
||||||
$tranbox.style.width = "0";
|
|
||||||
$tranbox.style.height = "0";
|
|
||||||
document.body.parentElement.appendChild($tranbox);
|
|
||||||
const shadowContainer = $tranbox.attachShadow({ mode: "closed" });
|
|
||||||
const emotionRoot = document.createElement("style");
|
|
||||||
const shadowRootElement = document.createElement("div");
|
|
||||||
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: APP_CONSTS.boxID,
|
|
||||||
prepend: true,
|
|
||||||
container: emotionRoot,
|
|
||||||
});
|
|
||||||
ReactDOM.createRoot(shadowRootElement).render(
|
|
||||||
<React.StrictMode>
|
|
||||||
<CacheProvider value={cache}>
|
|
||||||
<Slection
|
|
||||||
contextMenuType={contextMenuType}
|
|
||||||
tranboxSetting={tranboxSetting}
|
|
||||||
transApis={transApis}
|
|
||||||
uiLang={uiLang}
|
|
||||||
langDetector={langDetector}
|
|
||||||
/>
|
|
||||||
</CacheProvider>
|
|
||||||
</React.StrictMode>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 显示错误信息到页面顶部
|
* 显示错误信息到页面顶部
|
||||||
* @param {*} message
|
* @param {*} message
|
||||||
@@ -251,13 +159,13 @@ export async function run(isUserscript = false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 监听消息
|
// 监听消息
|
||||||
!isUserscript && runtimeListener(translator);
|
// !isUserscript && runtimeListener(translator);
|
||||||
|
|
||||||
// 输入框翻译
|
// 输入框翻译
|
||||||
inputTranslate(setting);
|
// inputTranslate(setting);
|
||||||
|
|
||||||
// 划词翻译
|
// 划词翻译
|
||||||
showTransbox(setting, rule);
|
// showTransbox(setting, rule);
|
||||||
|
|
||||||
// 浮球按钮
|
// 浮球按钮
|
||||||
await showFab(translator);
|
await showFab(translator);
|
||||||
|
|||||||
@@ -1399,9 +1399,9 @@ export const I18N = {
|
|||||||
zh_TW: `ShadowRoot`,
|
zh_TW: `ShadowRoot`,
|
||||||
},
|
},
|
||||||
richtext_alt: {
|
richtext_alt: {
|
||||||
zh: `富文本`,
|
zh: `保留富文本`,
|
||||||
en: `Rich Text`,
|
en: `Rich Text`,
|
||||||
zh_TW: `富文本`,
|
zh_TW: `保留富文本`,
|
||||||
},
|
},
|
||||||
transonly_alt: {
|
transonly_alt: {
|
||||||
zh: `隐藏原文`,
|
zh: `隐藏原文`,
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ export const MSG_OPEN_TRANBOX = "open_tranbox";
|
|||||||
export const MSG_TRANS_GETRULE = "trans_getrule";
|
export const MSG_TRANS_GETRULE = "trans_getrule";
|
||||||
export const MSG_TRANS_PUTRULE = "trans_putrule";
|
export const MSG_TRANS_PUTRULE = "trans_putrule";
|
||||||
export const MSG_TRANS_CURRULE = "trans_currule";
|
export const MSG_TRANS_CURRULE = "trans_currule";
|
||||||
|
export const MSG_TRANSBOX_TOGGLE = "transbox_toggle";
|
||||||
|
export const MSG_MOUSEHOVER_TOGGLE = "mousehover_toggle";
|
||||||
|
export const MSG_TRANSINPUT_TOGGLE = "transinput_toggle";
|
||||||
export const MSG_CONTEXT_MENUS = "context_menus";
|
export const MSG_CONTEXT_MENUS = "context_menus";
|
||||||
export const MSG_COMMAND_SHORTCUTS = "command_shortcuts";
|
export const MSG_COMMAND_SHORTCUTS = "command_shortcuts";
|
||||||
export const MSG_INJECT_JS = "inject_js";
|
export const MSG_INJECT_JS = "inject_js";
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ export const DEFAULT_RULE = {
|
|||||||
// transTiming: GLOBAL_KEY, // 翻译时机/鼠标悬停翻译 (暂时作废)
|
// transTiming: GLOBAL_KEY, // 翻译时机/鼠标悬停翻译 (暂时作废)
|
||||||
transTag: GLOBAL_KEY, // 译文元素标签
|
transTag: GLOBAL_KEY, // 译文元素标签
|
||||||
transTitle: GLOBAL_KEY, // 是否同时翻译页面标题
|
transTitle: GLOBAL_KEY, // 是否同时翻译页面标题
|
||||||
transSelected: GLOBAL_KEY, // 是否启用划词翻译
|
// transSelected: GLOBAL_KEY, // 是否启用划词翻译 (移回setting)
|
||||||
// detectRemote: GLOBAL_KEY, // 是否使用远程语言检测 (移回setting)
|
// detectRemote: GLOBAL_KEY, // 是否使用远程语言检测 (移回setting)
|
||||||
// skipLangs: [], // 不翻译的语言 (移回setting)
|
// skipLangs: [], // 不翻译的语言 (移回setting)
|
||||||
// fixerSelector: "", // 修复函数选择器 (暂时作废)
|
// fixerSelector: "", // 修复函数选择器 (暂时作废)
|
||||||
@@ -131,7 +131,7 @@ export const GLOBLA_RULE = {
|
|||||||
// transTiming: OPT_TIMING_PAGESCROLL, // 翻译时机/鼠标悬停翻译 (暂时作废)
|
// transTiming: OPT_TIMING_PAGESCROLL, // 翻译时机/鼠标悬停翻译 (暂时作废)
|
||||||
transTag: DEFAULT_TRANS_TAG, // 译文元素标签
|
transTag: DEFAULT_TRANS_TAG, // 译文元素标签
|
||||||
transTitle: "false", // 是否同时翻译页面标题
|
transTitle: "false", // 是否同时翻译页面标题
|
||||||
transSelected: "true", // 是否启用划词翻译
|
// transSelected: "true", // 是否启用划词翻译 (移回setting)
|
||||||
// detectRemote: "true", // 是否使用远程语言检测 (移回setting)
|
// detectRemote: "true", // 是否使用远程语言检测 (移回setting)
|
||||||
// skipLangs: [], // 不翻译的语言 (移回setting)
|
// skipLangs: [], // 不翻译的语言 (移回setting)
|
||||||
// fixerSelector: "", // 修复函数选择器 (暂时作废)
|
// fixerSelector: "", // 修复函数选择器 (暂时作废)
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ export const OPT_TRANBOX_TRIGGER_ALL = [
|
|||||||
];
|
];
|
||||||
export const DEFAULT_TRANBOX_SHORTCUT = ["AltLeft", "KeyS"];
|
export const DEFAULT_TRANBOX_SHORTCUT = ["AltLeft", "KeyS"];
|
||||||
export const DEFAULT_TRANBOX_SETTING = {
|
export const DEFAULT_TRANBOX_SETTING = {
|
||||||
// transOpen: true, // 是否启用划词翻译(作废,移至rule)
|
transOpen: true, // 是否启用划词翻译
|
||||||
apiSlug: OPT_TRANS_MICROSOFT,
|
apiSlug: OPT_TRANS_MICROSOFT,
|
||||||
fromLang: "auto",
|
fromLang: "auto",
|
||||||
toLang: "zh-CN",
|
toLang: "zh-CN",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
OPT_LANGS_LIST,
|
OPT_LANGS_LIST,
|
||||||
DEFAULT_API_SETTING,
|
DEFAULT_API_SETTING,
|
||||||
} from "../config";
|
} from "../config";
|
||||||
import { genEventName, removeEndchar, matchInputStr, sleep } from "./utils";
|
import { genEventName, removeEndchar, matchInputStr } from "./utils";
|
||||||
import { stepShortcutRegister } from "./shortcut";
|
import { stepShortcutRegister } from "./shortcut";
|
||||||
import { apiTranslate } from "../apis";
|
import { apiTranslate } from "../apis";
|
||||||
import { loadingSvg } from "./svg";
|
import { loadingSvg } from "./svg";
|
||||||
@@ -18,34 +18,20 @@ function isEditAbleNode(node) {
|
|||||||
return node.hasAttribute("contenteditable");
|
return node.hasAttribute("contenteditable");
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectContent(node) {
|
function replaceContentEditableText(node, newText) {
|
||||||
node.focus();
|
node.focus();
|
||||||
|
const selection = window.getSelection();
|
||||||
|
if (!selection) return;
|
||||||
|
|
||||||
const range = document.createRange();
|
const range = document.createRange();
|
||||||
range.selectNodeContents(node);
|
range.selectNodeContents(node);
|
||||||
|
|
||||||
const selection = window.getSelection();
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
}
|
|
||||||
|
|
||||||
function pasteContentEvent(node, text) {
|
range.deleteContents();
|
||||||
node.focus();
|
const textNode = document.createTextNode(newText);
|
||||||
const data = new DataTransfer();
|
range.insertNode(textNode);
|
||||||
data.setData("text/plain", text);
|
|
||||||
|
|
||||||
const event = new ClipboardEvent("paste", { clipboardData: data });
|
|
||||||
document.dispatchEvent(event);
|
|
||||||
data.clearData();
|
|
||||||
}
|
|
||||||
|
|
||||||
function pasteContentCommand(node, text) {
|
|
||||||
node.focus();
|
|
||||||
document.execCommand("insertText", false, text);
|
|
||||||
}
|
|
||||||
|
|
||||||
function collapseToEnd(node) {
|
|
||||||
node.focus();
|
|
||||||
const selection = window.getSelection();
|
|
||||||
selection.collapseToEnd();
|
selection.collapseToEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,144 +43,205 @@ function getNodeText(node) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function addLoading(node, loadingId) {
|
function addLoading(node, loadingId) {
|
||||||
|
const rect = node.getBoundingClientRect();
|
||||||
const div = document.createElement("div");
|
const div = document.createElement("div");
|
||||||
div.id = loadingId;
|
div.id = loadingId;
|
||||||
div.innerHTML = loadingSvg;
|
div.innerHTML = loadingSvg;
|
||||||
div.style.cssText = `
|
div.style.cssText = `
|
||||||
width: ${node.offsetWidth}px;
|
position: fixed;
|
||||||
height: ${node.offsetHeight}px;
|
left: ${rect.left}px;
|
||||||
line-height: ${node.offsetHeight}px;
|
top: ${rect.top}px;
|
||||||
position: absolute;
|
width: ${rect.width}px;
|
||||||
text-align: center;
|
height: ${rect.height}px;
|
||||||
left: ${node.offsetLeft}px;
|
line-height: ${rect.height}px;
|
||||||
top: ${node.offsetTop}px;
|
text-align: center;
|
||||||
z-index: 2147483647;
|
z-index: 2147483647;
|
||||||
|
pointer-events: none; /* 允许点击穿透 */
|
||||||
`;
|
`;
|
||||||
node.offsetParent?.appendChild(div);
|
document.body.appendChild(div);
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeLoading(node, loadingId) {
|
function removeLoading(loadingId) {
|
||||||
const div = node.offsetParent.querySelector(`#${loadingId}`);
|
const div = document.getElementById(loadingId);
|
||||||
if (div) {
|
if (div) div.remove();
|
||||||
div.remove();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 输入框翻译
|
* 输入框翻译
|
||||||
*/
|
*/
|
||||||
export default function inputTranslate({
|
export class InputTranslator {
|
||||||
inputRule: {
|
#config;
|
||||||
transOpen,
|
#unregisterShortcut = null;
|
||||||
triggerShortcut,
|
#isEnabled = false;
|
||||||
apiSlug,
|
#triggerShortcut; // 用于缓存快捷键
|
||||||
fromLang,
|
|
||||||
toLang,
|
constructor({ inputRule = DEFAULT_INPUT_RULE, transApis = [] } = {}) {
|
||||||
triggerCount,
|
this.#config = { inputRule, transApis };
|
||||||
triggerTime,
|
|
||||||
transSign,
|
const { triggerShortcut: initialTriggerShortcut } = this.#config.inputRule;
|
||||||
} = DEFAULT_INPUT_RULE,
|
if (initialTriggerShortcut && initialTriggerShortcut.length > 0) {
|
||||||
transApis,
|
this.#triggerShortcut = initialTriggerShortcut;
|
||||||
}) {
|
} else {
|
||||||
if (!transOpen) {
|
this.#triggerShortcut = DEFAULT_INPUT_SHORTCUT;
|
||||||
return;
|
}
|
||||||
|
|
||||||
|
if (this.#config.inputRule.transOpen) {
|
||||||
|
this.enable();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const apiSetting =
|
/**
|
||||||
transApis.find((api) => api.apiSlug === apiSlug) || DEFAULT_API_SETTING;
|
* 启用输入翻译功能
|
||||||
if (triggerShortcut.length === 0) {
|
*/
|
||||||
triggerShortcut = DEFAULT_INPUT_SHORTCUT;
|
enable() {
|
||||||
triggerCount = 1;
|
if (this.#isEnabled || !this.#config.inputRule.transOpen) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { triggerCount, triggerTime } = this.#config.inputRule;
|
||||||
|
this.#unregisterShortcut = stepShortcutRegister(
|
||||||
|
this.#triggerShortcut,
|
||||||
|
this.#handleTranslate.bind(this),
|
||||||
|
triggerCount,
|
||||||
|
triggerTime
|
||||||
|
);
|
||||||
|
|
||||||
|
this.#isEnabled = true;
|
||||||
|
kissLog("Input Translator enabled.");
|
||||||
}
|
}
|
||||||
|
|
||||||
stepShortcutRegister(
|
/**
|
||||||
triggerShortcut,
|
* 禁用输入翻译功能
|
||||||
async () => {
|
*/
|
||||||
let node = document.activeElement;
|
disable() {
|
||||||
|
if (!this.#isEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.#unregisterShortcut) {
|
||||||
|
this.#unregisterShortcut();
|
||||||
|
this.#unregisterShortcut = null;
|
||||||
|
}
|
||||||
|
this.#isEnabled = false;
|
||||||
|
kissLog("Input Translator disabled.");
|
||||||
|
}
|
||||||
|
|
||||||
if (!node) {
|
/**
|
||||||
return;
|
* 切换启用/禁用状态
|
||||||
}
|
*/
|
||||||
|
toggle() {
|
||||||
|
if (this.#isEnabled) {
|
||||||
|
this.disable();
|
||||||
|
} else {
|
||||||
|
this.enable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
while (node.shadowRoot) {
|
/**
|
||||||
node = node.shadowRoot.activeElement;
|
* 翻译核心逻辑
|
||||||
}
|
* @private
|
||||||
|
*/
|
||||||
|
async #handleTranslate() {
|
||||||
|
let node = document.activeElement;
|
||||||
|
if (!node) return;
|
||||||
|
|
||||||
if (!isInputNode(node) && !isEditAbleNode(node)) {
|
while (node.shadowRoot && node.shadowRoot.activeElement) {
|
||||||
return;
|
node = node.shadowRoot.activeElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
let initText = getNodeText(node);
|
if (!isInputNode(node) && !isEditAbleNode(node)) {
|
||||||
if (triggerShortcut.length === 1 && triggerShortcut[0].length === 1) {
|
return;
|
||||||
// todo: remove multiple char
|
}
|
||||||
initText = removeEndchar(initText, triggerShortcut[0], triggerCount);
|
|
||||||
}
|
|
||||||
if (!initText.trim()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let text = initText;
|
const { apiSlug, transSign, triggerCount } = this.#config.inputRule;
|
||||||
if (transSign) {
|
let { fromLang, toLang } = this.#config.inputRule;
|
||||||
const res = matchInputStr(text, transSign);
|
|
||||||
if (res) {
|
let initText = getNodeText(node);
|
||||||
let lang = res[1];
|
|
||||||
if (lang === "zh" || lang === "cn") {
|
if (
|
||||||
lang = "zh-CN";
|
this.#triggerShortcut.length === 1 &&
|
||||||
} else if (lang === "tw" || lang === "hk") {
|
this.#triggerShortcut[0].length === 1
|
||||||
lang = "zh-TW";
|
) {
|
||||||
}
|
initText = removeEndchar(
|
||||||
if (lang && OPT_LANGS_LIST.includes(lang)) {
|
initText,
|
||||||
toLang = lang;
|
this.#triggerShortcut[0],
|
||||||
}
|
triggerCount
|
||||||
text = res[2];
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!initText.trim()) return;
|
||||||
|
|
||||||
|
let text = initText;
|
||||||
|
if (transSign) {
|
||||||
|
const res = matchInputStr(text, transSign);
|
||||||
|
if (res) {
|
||||||
|
let lang = res[1];
|
||||||
|
if (lang === "zh" || lang === "cn") lang = "zh-CN";
|
||||||
|
else if (lang === "tw" || lang === "hk") lang = "zh-TW";
|
||||||
|
|
||||||
|
if (lang && OPT_LANGS_LIST.includes(lang)) {
|
||||||
|
toLang = lang;
|
||||||
}
|
}
|
||||||
|
text = res[2];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// console.log("input -->", text);
|
const apiSetting =
|
||||||
|
this.#config.transApis.find((api) => api.apiSlug === apiSlug) ||
|
||||||
|
DEFAULT_API_SETTING;
|
||||||
|
const loadingId = "kiss-loading-" + genEventName();
|
||||||
|
|
||||||
const loadingId = "kiss-" + genEventName();
|
try {
|
||||||
try {
|
addLoading(node, loadingId);
|
||||||
addLoading(node, loadingId);
|
|
||||||
|
|
||||||
const [trText, isSame] = await apiTranslate({
|
const [trText, isSame] = await apiTranslate({
|
||||||
apiSlug,
|
text,
|
||||||
text,
|
fromLang,
|
||||||
fromLang,
|
toLang,
|
||||||
toLang,
|
apiSlug,
|
||||||
apiSetting,
|
apiSetting,
|
||||||
});
|
});
|
||||||
if (!trText || isSame) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isInputNode(node)) {
|
if (!trText || isSame) return;
|
||||||
node.value = trText;
|
|
||||||
node.dispatchEvent(
|
|
||||||
new Event("input", { bubbles: true, cancelable: true })
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
selectContent(node);
|
if (isInputNode(node)) {
|
||||||
await sleep(200);
|
node.value = trText;
|
||||||
|
node.dispatchEvent(
|
||||||
pasteContentEvent(node, trText);
|
new Event("input", { bubbles: true, cancelable: true })
|
||||||
await sleep(200);
|
);
|
||||||
|
} else {
|
||||||
// todo: use includes?
|
replaceContentEditableText(node, trText);
|
||||||
if (getNodeText(node).startsWith(initText)) {
|
|
||||||
pasteContentCommand(node, trText);
|
|
||||||
await sleep(100);
|
|
||||||
} else {
|
|
||||||
collapseToEnd(node);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
kissLog("translate input", err);
|
|
||||||
} finally {
|
|
||||||
removeLoading(node, loadingId);
|
|
||||||
}
|
}
|
||||||
},
|
} catch (err) {
|
||||||
triggerCount,
|
kissLog("Translate input error:", err);
|
||||||
triggerTime
|
} finally {
|
||||||
);
|
removeLoading(loadingId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新配置
|
||||||
|
*/
|
||||||
|
updateConfig({ inputRule, transApis }) {
|
||||||
|
const wasEnabled = this.#isEnabled;
|
||||||
|
if (wasEnabled) {
|
||||||
|
this.disable();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inputRule) {
|
||||||
|
this.#config.inputRule = inputRule;
|
||||||
|
}
|
||||||
|
if (transApis) {
|
||||||
|
this.#config.transApis = transApis;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { triggerShortcut: initialTriggerShortcut } = this.#config.inputRule;
|
||||||
|
this.#triggerShortcut =
|
||||||
|
initialTriggerShortcut && initialTriggerShortcut.length > 0
|
||||||
|
? initialTriggerShortcut
|
||||||
|
: DEFAULT_INPUT_SHORTCUT;
|
||||||
|
|
||||||
|
if (wasEnabled) {
|
||||||
|
this.enable();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,7 +78,6 @@ export const matchRule = async (href, { injectRules, subrulesList }) => {
|
|||||||
"hasShadowroot",
|
"hasShadowroot",
|
||||||
"transTag",
|
"transTag",
|
||||||
"transTitle",
|
"transTitle",
|
||||||
"transSelected",
|
|
||||||
// "detectRemote",
|
// "detectRemote",
|
||||||
// "fixerFunc",
|
// "fixerFunc",
|
||||||
].forEach((key) => {
|
].forEach((key) => {
|
||||||
@@ -153,7 +152,6 @@ export const checkRules = (rules) => {
|
|||||||
// transTiming,
|
// transTiming,
|
||||||
transTag,
|
transTag,
|
||||||
transTitle,
|
transTitle,
|
||||||
transSelected,
|
|
||||||
// detectRemote,
|
// detectRemote,
|
||||||
// skipLangs,
|
// skipLangs,
|
||||||
// fixerSelector,
|
// fixerSelector,
|
||||||
@@ -186,7 +184,6 @@ export const checkRules = (rules) => {
|
|||||||
// transTiming: matchValue([GLOBAL_KEY, ...OPT_TIMING_ALL], transTiming),
|
// transTiming: matchValue([GLOBAL_KEY, ...OPT_TIMING_ALL], transTiming),
|
||||||
transTag: matchValue([GLOBAL_KEY, "span", "font"], transTag),
|
transTag: matchValue([GLOBAL_KEY, "span", "font"], transTag),
|
||||||
transTitle: matchValue([GLOBAL_KEY, "true", "false"], transTitle),
|
transTitle: matchValue([GLOBAL_KEY, "true", "false"], transTitle),
|
||||||
transSelected: matchValue([GLOBAL_KEY, "true", "false"], transSelected),
|
|
||||||
// detectRemote: matchValue([GLOBAL_KEY, "true", "false"], detectRemote),
|
// detectRemote: matchValue([GLOBAL_KEY, "true", "false"], detectRemote),
|
||||||
// skipLangs: type(skipLangs) === "array" ? skipLangs : [],
|
// skipLangs: type(skipLangs) === "array" ? skipLangs : [],
|
||||||
// fixerSelector: type(fixerSelector) === "string" ? fixerSelector : "",
|
// fixerSelector: type(fixerSelector) === "string" ? fixerSelector : "",
|
||||||
|
|||||||
95
src/libs/tranbox.js
Normal file
95
src/libs/tranbox.js
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import React from "react";
|
||||||
|
import ReactDOM from "react-dom/client";
|
||||||
|
import createCache from "@emotion/cache";
|
||||||
|
import { CacheProvider } from "@emotion/react";
|
||||||
|
import Slection from "../views/Selection";
|
||||||
|
import { DEFAULT_TRANBOX_SETTING, APP_CONSTS } from "../config";
|
||||||
|
|
||||||
|
export class TransboxManager {
|
||||||
|
#container = null;
|
||||||
|
#reactRoot = null;
|
||||||
|
#shadowContainer = null;
|
||||||
|
#props = {};
|
||||||
|
|
||||||
|
constructor(initialProps = {}) {
|
||||||
|
this.#props = initialProps;
|
||||||
|
|
||||||
|
const { tranboxSetting = DEFAULT_TRANBOX_SETTING } = this.#props;
|
||||||
|
if (tranboxSetting?.transOpen) {
|
||||||
|
this.enable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isEnabled() {
|
||||||
|
return (
|
||||||
|
!!this.#container && document.body.parentElement.contains(this.#container)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
enable() {
|
||||||
|
if (!this.isEnabled()) {
|
||||||
|
this.#container = document.createElement("div");
|
||||||
|
this.#container.setAttribute("id", APP_CONSTS.boxID);
|
||||||
|
this.#container.style.cssText =
|
||||||
|
"font-size: 0; width: 0; height: 0; border: 0; padding: 0; margin: 0;";
|
||||||
|
document.body.parentElement.appendChild(this.#container);
|
||||||
|
|
||||||
|
this.#shadowContainer = this.#container.attachShadow({ mode: "closed" });
|
||||||
|
const emotionRoot = document.createElement("style");
|
||||||
|
const shadowRootElement = document.createElement("div");
|
||||||
|
shadowRootElement.classList.add(`${APP_CONSTS.boxID}_warpper`);
|
||||||
|
this.#shadowContainer.appendChild(emotionRoot);
|
||||||
|
this.#shadowContainer.appendChild(shadowRootElement);
|
||||||
|
const cache = createCache({
|
||||||
|
key: APP_CONSTS.boxID,
|
||||||
|
prepend: true,
|
||||||
|
container: emotionRoot,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.#reactRoot = ReactDOM.createRoot(shadowRootElement);
|
||||||
|
this.CacheProvider = ({ children }) => (
|
||||||
|
<CacheProvider value={cache}>{children}</CacheProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const AppProvider = this.CacheProvider;
|
||||||
|
this.#reactRoot.render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<AppProvider>
|
||||||
|
<Slection {...this.#props} />
|
||||||
|
</AppProvider>
|
||||||
|
</React.StrictMode>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
disable() {
|
||||||
|
if (!this.isEnabled() || !this.#reactRoot) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.#reactRoot.unmount();
|
||||||
|
this.#container.remove();
|
||||||
|
this.#container = null;
|
||||||
|
this.#reactRoot = null;
|
||||||
|
this.#shadowContainer = null;
|
||||||
|
this.CacheProvider = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle() {
|
||||||
|
if (this.isEnabled()) {
|
||||||
|
this.disable();
|
||||||
|
} else {
|
||||||
|
this.enable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
update(newProps) {
|
||||||
|
this.#props = { ...this.#props, ...newProps };
|
||||||
|
if (this.isEnabled()) {
|
||||||
|
if (!this.#props.tranboxSetting?.transOpen) {
|
||||||
|
this.disable();
|
||||||
|
} else {
|
||||||
|
this.enable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,14 @@ import {
|
|||||||
// DEFAULT_MOUSEHOVER_KEY,
|
// DEFAULT_MOUSEHOVER_KEY,
|
||||||
OPT_STYLE_NONE,
|
OPT_STYLE_NONE,
|
||||||
DEFAULT_API_SETTING,
|
DEFAULT_API_SETTING,
|
||||||
|
MSG_TRANS_TOGGLE,
|
||||||
|
MSG_TRANS_TOGGLE_STYLE,
|
||||||
|
MSG_TRANS_GETRULE,
|
||||||
|
MSG_TRANS_PUTRULE,
|
||||||
|
MSG_OPEN_TRANBOX,
|
||||||
|
MSG_TRANSBOX_TOGGLE,
|
||||||
|
MSG_MOUSEHOVER_TOGGLE,
|
||||||
|
MSG_TRANSINPUT_TOGGLE,
|
||||||
} from "../config";
|
} from "../config";
|
||||||
import interpreter from "./interpreter";
|
import interpreter from "./interpreter";
|
||||||
import { ShadowRootMonitor } from "./shadowroot";
|
import { ShadowRootMonitor } from "./shadowroot";
|
||||||
@@ -25,6 +33,10 @@ import { genTextClass } from "./style";
|
|||||||
import { loadingSvg } from "./svg";
|
import { loadingSvg } from "./svg";
|
||||||
import { shortcutRegister } from "./shortcut";
|
import { shortcutRegister } from "./shortcut";
|
||||||
import { tryDetectLang } from "./detect";
|
import { tryDetectLang } from "./detect";
|
||||||
|
import { browser } from "./browser";
|
||||||
|
import { isIframe, sendIframeMsg } from "./iframe";
|
||||||
|
import { TransboxManager } from "./tranbox";
|
||||||
|
import { InputTranslator } from "./inputTranslate";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class Translator
|
* @class Translator
|
||||||
@@ -269,6 +281,10 @@ export class Translator {
|
|||||||
#textClass = {}; // 译文样式class
|
#textClass = {}; // 译文样式class
|
||||||
#textSheet = ""; // 译文样式字典
|
#textSheet = ""; // 译文样式字典
|
||||||
|
|
||||||
|
#isUserscript = false;
|
||||||
|
#transboxManager = null; // 划词翻译
|
||||||
|
#inputTranslator = null; // 输入框翻译
|
||||||
|
|
||||||
#observedNodes = new WeakSet(); // 存储所有被识别出的、可翻译的 DOM 节点单元
|
#observedNodes = new WeakSet(); // 存储所有被识别出的、可翻译的 DOM 节点单元
|
||||||
#translationNodes = new WeakMap(); // 存储所有插入到页面的译文节点
|
#translationNodes = new WeakMap(); // 存储所有插入到页面的译文节点
|
||||||
#viewNodes = new Set(); // 当前在可视范围内的单元
|
#viewNodes = new Set(); // 当前在可视范围内的单元
|
||||||
@@ -293,9 +309,10 @@ export class Translator {
|
|||||||
return `${Translator.BUILTIN_IGNORE_SELECTOR}, ${this.#rule.ignoreSelector}`;
|
return `${Translator.BUILTIN_IGNORE_SELECTOR}, ${this.#rule.ignoreSelector}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(rule = {}, setting = {}) {
|
constructor(rule = {}, setting = {}, isUserscript) {
|
||||||
this.#setting = { ...Translator.DEFAULT_OPTIONS, ...setting };
|
this.#setting = { ...Translator.DEFAULT_OPTIONS, ...setting };
|
||||||
this.#rule = { ...Translator.DEFAULT_RULE, ...rule };
|
this.#rule = { ...Translator.DEFAULT_RULE, ...rule };
|
||||||
|
this.#isUserscript = isUserscript;
|
||||||
this.#eventName = genEventName();
|
this.#eventName = genEventName();
|
||||||
this.#docInfo = {
|
this.#docInfo = {
|
||||||
title: document.title,
|
title: document.title,
|
||||||
@@ -326,6 +343,19 @@ export class Translator {
|
|||||||
this.#enableMouseHover();
|
this.#enableMouseHover();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isIframe) {
|
||||||
|
// 监听后端事件
|
||||||
|
if (!isUserscript) {
|
||||||
|
this.#runtimeListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 划词翻译
|
||||||
|
this.#transboxManager = new TransboxManager(this.setting);
|
||||||
|
|
||||||
|
// 输入框翻译
|
||||||
|
this.#inputTranslator = new InputTranslator(this.setting);
|
||||||
|
}
|
||||||
|
|
||||||
if (document.readyState === "loading") {
|
if (document.readyState === "loading") {
|
||||||
document.addEventListener("DOMContentLoaded", () => this.#run());
|
document.addEventListener("DOMContentLoaded", () => this.#run());
|
||||||
} else {
|
} else {
|
||||||
@@ -368,6 +398,43 @@ export class Translator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 监听后端事件
|
||||||
|
#runtimeListener() {
|
||||||
|
browser?.runtime.onMessage.addListener(async ({ action, args }) => {
|
||||||
|
switch (action) {
|
||||||
|
case MSG_TRANS_TOGGLE:
|
||||||
|
this.toggle();
|
||||||
|
sendIframeMsg(MSG_TRANS_TOGGLE);
|
||||||
|
break;
|
||||||
|
case MSG_TRANS_TOGGLE_STYLE:
|
||||||
|
this.toggleStyle();
|
||||||
|
sendIframeMsg(MSG_TRANS_TOGGLE_STYLE);
|
||||||
|
break;
|
||||||
|
case MSG_TRANS_GETRULE:
|
||||||
|
break;
|
||||||
|
case MSG_TRANS_PUTRULE:
|
||||||
|
this.updateRule(args);
|
||||||
|
sendIframeMsg(MSG_TRANS_PUTRULE, args);
|
||||||
|
break;
|
||||||
|
case MSG_OPEN_TRANBOX:
|
||||||
|
window.dispatchEvent(new CustomEvent(MSG_OPEN_TRANBOX));
|
||||||
|
break;
|
||||||
|
case MSG_TRANSBOX_TOGGLE:
|
||||||
|
this.toggleTransbox();
|
||||||
|
break;
|
||||||
|
case MSG_MOUSEHOVER_TOGGLE:
|
||||||
|
this.toggleMouseHover();
|
||||||
|
break;
|
||||||
|
case MSG_TRANSINPUT_TOGGLE:
|
||||||
|
this.toggleInputTranslate();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return { error: `message action is unavailable: ${action}` };
|
||||||
|
}
|
||||||
|
return { rule: this.rule, setting: this.setting };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#createPlaceholderRegex() {
|
#createPlaceholderRegex() {
|
||||||
const escapedStart = Translator.escapeRegex(
|
const escapedStart = Translator.escapeRegex(
|
||||||
Translator.PLACEHOLDER.startDelimiter
|
Translator.PLACEHOLDER.startDelimiter
|
||||||
@@ -671,14 +738,16 @@ export class Translator {
|
|||||||
|
|
||||||
// 开始/重新监控节点
|
// 开始/重新监控节点
|
||||||
#startObserveNode(node) {
|
#startObserveNode(node) {
|
||||||
if (this.#observedNodes.has(node)) {
|
// 未监控
|
||||||
// 已监控,但未处理状态,且在可视范围
|
if (!this.#observedNodes.has(node)) {
|
||||||
if (!this.#processedNodes.has(node) && this.#viewNodes.has(node)) {
|
|
||||||
this.#reIO(node);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.#observedNodes.add(node);
|
this.#observedNodes.add(node);
|
||||||
this.#io.observe(node);
|
this.#io.observe(node);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 已监控,但未处理状态,且在可视范围
|
||||||
|
if (!this.#processedNodes.has(node) && this.#viewNodes.has(node)) {
|
||||||
|
this.#reIO(node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1369,6 +1438,19 @@ export class Translator {
|
|||||||
this.updateRule({ textStyle });
|
this.updateRule({ textStyle });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 切换划词翻译
|
||||||
|
toggleTransbox() {
|
||||||
|
this.#setting.tranboxSetting.transOpen =
|
||||||
|
!this.#setting.tranboxSetting.transOpen;
|
||||||
|
this.#transboxManager?.toggle();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换输入框翻译
|
||||||
|
toggleInputTranslate() {
|
||||||
|
this.#setting.inputRule.transOpen = !this.#setting.inputRule.transOpen;
|
||||||
|
this.#inputTranslator?.toggle();
|
||||||
|
}
|
||||||
|
|
||||||
// 停止运行
|
// 停止运行
|
||||||
stop() {
|
stop() {
|
||||||
this.disable();
|
this.disable();
|
||||||
|
|||||||
@@ -112,7 +112,6 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
|
|||||||
// transTiming = OPT_TIMING_PAGESCROLL,
|
// transTiming = OPT_TIMING_PAGESCROLL,
|
||||||
transTag = DEFAULT_TRANS_TAG,
|
transTag = DEFAULT_TRANS_TAG,
|
||||||
transTitle = "false",
|
transTitle = "false",
|
||||||
transSelected = "true",
|
|
||||||
// detectRemote = "true",
|
// detectRemote = "true",
|
||||||
// skipLangs = [],
|
// skipLangs = [],
|
||||||
// fixerSelector = "",
|
// fixerSelector = "",
|
||||||
@@ -337,22 +336,6 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
|
|||||||
</TextField>
|
</TextField>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Grid item xs={12} sm={12} md={6} lg={3}>
|
|
||||||
<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={12} md={6} lg={3}>
|
<Grid item xs={12} sm={12} md={6} lg={3}>
|
||||||
<TextField
|
<TextField
|
||||||
select
|
select
|
||||||
|
|||||||
@@ -12,12 +12,13 @@ import {
|
|||||||
OPT_DICT_BAIDU,
|
OPT_DICT_BAIDU,
|
||||||
} from "../../config";
|
} from "../../config";
|
||||||
import ShortcutInput from "./ShortcutInput";
|
import ShortcutInput from "./ShortcutInput";
|
||||||
|
import FormControlLabel from "@mui/material/FormControlLabel";
|
||||||
|
import Switch from "@mui/material/Switch";
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
import { limitNumber } from "../../libs/utils";
|
import { limitNumber } from "../../libs/utils";
|
||||||
import { useTranbox } from "../../hooks/Tranbox";
|
import { useTranbox } from "../../hooks/Tranbox";
|
||||||
import { isExt } from "../../libs/client";
|
import { isExt } from "../../libs/client";
|
||||||
import { useApiList } from "../../hooks/Api";
|
import { useApiList } from "../../hooks/Api";
|
||||||
import Alert from "@mui/material/Alert";
|
|
||||||
|
|
||||||
export default function Tranbox() {
|
export default function Tranbox() {
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
@@ -49,6 +50,7 @@ export default function Tranbox() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
transOpen,
|
||||||
apiSlug,
|
apiSlug,
|
||||||
fromLang,
|
fromLang,
|
||||||
toLang,
|
toLang,
|
||||||
@@ -70,7 +72,19 @@ export default function Tranbox() {
|
|||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Stack spacing={3}>
|
<Stack spacing={3}>
|
||||||
<Alert severity="info">{i18n("selected_translation_alert")}</Alert>
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Switch
|
||||||
|
size="small"
|
||||||
|
name="transOpen"
|
||||||
|
checked={transOpen}
|
||||||
|
onChange={() => {
|
||||||
|
updateTranbox({ transOpen: !transOpen });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={i18n("toggle_selection_translate")}
|
||||||
|
/>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Grid container spacing={2} columns={12}>
|
<Grid container spacing={2} columns={12}>
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ import {
|
|||||||
MSG_OPEN_OPTIONS,
|
MSG_OPEN_OPTIONS,
|
||||||
MSG_SAVE_RULE,
|
MSG_SAVE_RULE,
|
||||||
MSG_COMMAND_SHORTCUTS,
|
MSG_COMMAND_SHORTCUTS,
|
||||||
|
MSG_TRANSBOX_TOGGLE,
|
||||||
|
MSG_MOUSEHOVER_TOGGLE,
|
||||||
|
MSG_TRANSINPUT_TOGGLE,
|
||||||
OPT_LANGS_FROM,
|
OPT_LANGS_FROM,
|
||||||
OPT_LANGS_TO,
|
OPT_LANGS_TO,
|
||||||
OPT_STYLE_ALL,
|
OPT_STYLE_ALL,
|
||||||
@@ -35,9 +38,7 @@ import { parseUrlPattern } from "../../libs/utils";
|
|||||||
export default function Popup({ setShowPopup, translator }) {
|
export default function Popup({ setShowPopup, translator }) {
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const [rule, setRule] = useState(translator?.rule);
|
const [rule, setRule] = useState(translator?.rule);
|
||||||
const [transApis, setTransApis] = useState(
|
const [setting, setSetting] = useState(translator?.setting);
|
||||||
translator?.setting?.transApis || []
|
|
||||||
);
|
|
||||||
const [commands, setCommands] = useState({});
|
const [commands, setCommands] = useState({});
|
||||||
|
|
||||||
const handleOpenSetting = () => {
|
const handleOpenSetting = () => {
|
||||||
@@ -66,6 +67,66 @@ export default function Popup({ setShowPopup, translator }) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleTransboxToggle = async (e) => {
|
||||||
|
try {
|
||||||
|
setSetting((pre) => ({
|
||||||
|
...pre,
|
||||||
|
tranboxSetting: { ...pre.tranboxSetting, transOpen: e.target.checked },
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!translator) {
|
||||||
|
await sendTabMsg(MSG_TRANSBOX_TOGGLE);
|
||||||
|
} else {
|
||||||
|
translator.toggleTransbox();
|
||||||
|
sendIframeMsg(MSG_TRANSBOX_TOGGLE);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
kissLog("toggle transbox", err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMousehoverToggle = async (e) => {
|
||||||
|
try {
|
||||||
|
setSetting((pre) => ({
|
||||||
|
...pre,
|
||||||
|
mouseHoverSetting: {
|
||||||
|
...pre.mouseHoverSetting,
|
||||||
|
useMouseHover: e.target.checked,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!translator) {
|
||||||
|
await sendTabMsg(MSG_MOUSEHOVER_TOGGLE);
|
||||||
|
} else {
|
||||||
|
translator.toggleMouseHover();
|
||||||
|
sendIframeMsg(MSG_MOUSEHOVER_TOGGLE);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
kissLog("toggle mousehover", err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInputTransToggle = async (e) => {
|
||||||
|
try {
|
||||||
|
setSetting((pre) => ({
|
||||||
|
...pre,
|
||||||
|
inputRule: {
|
||||||
|
...pre.inputRule,
|
||||||
|
transOpen: e.target.checked,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!translator) {
|
||||||
|
await sendTabMsg(MSG_TRANSINPUT_TOGGLE);
|
||||||
|
} else {
|
||||||
|
translator.toggleInputTranslate();
|
||||||
|
sendIframeMsg(MSG_TRANSINPUT_TOGGLE);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
kissLog("toggle inputtrans", err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleChange = async (e) => {
|
const handleChange = async (e) => {
|
||||||
try {
|
try {
|
||||||
const { name, value } = e.target;
|
const { name, value } = e.target;
|
||||||
@@ -121,7 +182,7 @@ export default function Popup({ setShowPopup, translator }) {
|
|||||||
const res = await sendTabMsg(MSG_TRANS_GETRULE);
|
const res = await sendTabMsg(MSG_TRANS_GETRULE);
|
||||||
if (!res.error) {
|
if (!res.error) {
|
||||||
setRule(res.rule);
|
setRule(res.rule);
|
||||||
setTransApis(res.setting.transApis);
|
setSetting(res.setting);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
kissLog("query rule", err);
|
kissLog("query rule", err);
|
||||||
@@ -155,15 +216,19 @@ export default function Popup({ setShowPopup, translator }) {
|
|||||||
|
|
||||||
const optApis = useMemo(
|
const optApis = useMemo(
|
||||||
() =>
|
() =>
|
||||||
transApis
|
setting?.transApis
|
||||||
.filter((api) => !api.isDisabled)
|
.filter((api) => !api.isDisabled)
|
||||||
.map((api) => ({
|
.map((api) => ({
|
||||||
key: api.apiSlug,
|
key: api.apiSlug,
|
||||||
name: api.apiName || api.apiSlug,
|
name: api.apiName || api.apiSlug,
|
||||||
})),
|
})),
|
||||||
[transApis]
|
[setting]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const tranboxEnabled = setting?.tranboxSetting.transOpen;
|
||||||
|
const mouseHoverEnabled = setting?.mouseHoverSetting.useMouseHover;
|
||||||
|
const inputTransEnabled = setting?.inputRule.transOpen;
|
||||||
|
|
||||||
if (!rule) {
|
if (!rule) {
|
||||||
return (
|
return (
|
||||||
<Box minWidth={300}>
|
<Box minWidth={300}>
|
||||||
@@ -195,7 +260,7 @@ export default function Popup({ setShowPopup, translator }) {
|
|||||||
} = rule;
|
} = rule;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box width={320}>
|
<Box width={360}>
|
||||||
{!translator && (
|
{!translator && (
|
||||||
<>
|
<>
|
||||||
<Header />
|
<Header />
|
||||||
@@ -275,6 +340,48 @@ export default function Popup({ setShowPopup, translator }) {
|
|||||||
label={i18n("richtext_alt")}
|
label={i18n("richtext_alt")}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
<Grid item xs={6}>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Switch
|
||||||
|
size="small"
|
||||||
|
name="tranboxEnabled"
|
||||||
|
value={!tranboxEnabled}
|
||||||
|
checked={tranboxEnabled}
|
||||||
|
onChange={handleTransboxToggle}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={i18n("selection_translate")}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={6}>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Switch
|
||||||
|
size="small"
|
||||||
|
name="mouseHoverEnabled"
|
||||||
|
value={!mouseHoverEnabled}
|
||||||
|
checked={mouseHoverEnabled}
|
||||||
|
onChange={handleMousehoverToggle}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={i18n("mousehover_translate")}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={6}>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Switch
|
||||||
|
size="small"
|
||||||
|
name="inputTransEnabled"
|
||||||
|
value={!inputTransEnabled}
|
||||||
|
checked={inputTransEnabled}
|
||||||
|
onChange={handleInputTransToggle}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={i18n("input_translate")}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
|
|||||||
Reference in New Issue
Block a user