refactor: add TranslatorManager
This commit is contained in:
139
src/common.js
139
src/common.js
@@ -1,24 +1,11 @@
|
|||||||
import React from "react";
|
import { OPT_HIGHLIGHT_WORDS_DISABLE } from "./config";
|
||||||
import ReactDOM from "react-dom/client";
|
|
||||||
import Action from "./views/Action";
|
|
||||||
import createCache from "@emotion/cache";
|
|
||||||
import { CacheProvider } from "@emotion/react";
|
|
||||||
import {
|
|
||||||
MSG_TRANS_TOGGLE,
|
|
||||||
MSG_TRANS_TOGGLE_STYLE,
|
|
||||||
MSG_TRANS_PUTRULE,
|
|
||||||
APP_CONSTS,
|
|
||||||
OPT_HIGHLIGHT_WORDS_DISABLE,
|
|
||||||
} from "./config";
|
|
||||||
import {
|
import {
|
||||||
getFabWithDefault,
|
getFabWithDefault,
|
||||||
getSettingWithDefault,
|
getSettingWithDefault,
|
||||||
getWordsWithDefault,
|
getWordsWithDefault,
|
||||||
} from "./libs/storage";
|
} from "./libs/storage";
|
||||||
import { Translator } from "./libs/translator";
|
import { isIframe } from "./libs/iframe";
|
||||||
import { isIframe, sendIframeMsg } from "./libs/iframe";
|
import { genEventName } from "./libs/utils";
|
||||||
import { touchTapListener } from "./libs/touch";
|
|
||||||
import { debounce, genEventName } from "./libs/utils";
|
|
||||||
import { handlePing, injectScript } from "./libs/gm";
|
import { handlePing, injectScript } from "./libs/gm";
|
||||||
import { matchRule } from "./libs/rules";
|
import { matchRule } from "./libs/rules";
|
||||||
import { trySyncAllSubRules } from "./libs/subRules";
|
import { trySyncAllSubRules } from "./libs/subRules";
|
||||||
@@ -26,6 +13,7 @@ import { isInBlacklist } from "./libs/blacklist";
|
|||||||
import { runSubtitle } from "./subtitle/subtitle";
|
import { runSubtitle } from "./subtitle/subtitle";
|
||||||
import { logger } from "./libs/log";
|
import { logger } from "./libs/log";
|
||||||
import { injectInlineJs } from "./libs/injector";
|
import { injectInlineJs } from "./libs/injector";
|
||||||
|
import TranslatorManager from "./libs/translatorManager";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 油猴脚本设置页面
|
* 油猴脚本设置页面
|
||||||
@@ -48,62 +36,6 @@ function runSettingPage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* iframe 页面执行
|
|
||||||
* @param {*} translator
|
|
||||||
*/
|
|
||||||
function runIframe(translator) {
|
|
||||||
window.addEventListener("message", (e) => {
|
|
||||||
const { action, args } = e.data || {};
|
|
||||||
switch (action) {
|
|
||||||
case MSG_TRANS_TOGGLE:
|
|
||||||
translator?.toggle();
|
|
||||||
break;
|
|
||||||
case MSG_TRANS_TOGGLE_STYLE:
|
|
||||||
translator?.toggleStyle();
|
|
||||||
break;
|
|
||||||
case MSG_TRANS_PUTRULE:
|
|
||||||
translator.updateRule(args || {});
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 悬浮按钮
|
|
||||||
* @param {*} translator
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
async function showFab(translator) {
|
|
||||||
const fab = await getFabWithDefault();
|
|
||||||
const $action = document.createElement("div");
|
|
||||||
$action.id = APP_CONSTS.fabID;
|
|
||||||
$action.className = "notranslate";
|
|
||||||
$action.style.fontSize = "0";
|
|
||||||
$action.style.width = "0";
|
|
||||||
$action.style.height = "0";
|
|
||||||
document.body.parentElement.appendChild($action);
|
|
||||||
const shadowContainer = $action.attachShadow({ mode: "closed" });
|
|
||||||
const emotionRoot = document.createElement("style");
|
|
||||||
const shadowRootElement = document.createElement("div");
|
|
||||||
shadowRootElement.className = `${APP_CONSTS.fabID}_warpper notranslate`;
|
|
||||||
shadowContainer.appendChild(emotionRoot);
|
|
||||||
shadowContainer.appendChild(shadowRootElement);
|
|
||||||
const cache = createCache({
|
|
||||||
key: APP_CONSTS.fabID,
|
|
||||||
prepend: true,
|
|
||||||
container: emotionRoot,
|
|
||||||
});
|
|
||||||
ReactDOM.createRoot(shadowRootElement).render(
|
|
||||||
<React.StrictMode>
|
|
||||||
<CacheProvider value={cache}>
|
|
||||||
<Action translator={translator} fab={fab} />
|
|
||||||
</CacheProvider>
|
|
||||||
</React.StrictMode>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 显示错误信息到页面顶部
|
* 显示错误信息到页面顶部
|
||||||
* @param {*} message
|
* @param {*} message
|
||||||
@@ -166,22 +98,19 @@ function showErr(message) {
|
|||||||
setTimeout(removeBanner, 10000);
|
setTimeout(removeBanner, 10000);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
async function getFavWords(rule) {
|
||||||
* 监听触屏操作
|
if (
|
||||||
* @param {*} translator
|
rule.highlightWords &&
|
||||||
* @returns
|
rule.highlightWords !== OPT_HIGHLIGHT_WORDS_DISABLE
|
||||||
*/
|
) {
|
||||||
function touchOperation(translator) {
|
try {
|
||||||
const { touchTranslate = 2 } = translator.setting;
|
return Object.keys(await getWordsWithDefault());
|
||||||
if (touchTranslate === 0) {
|
} catch (err) {
|
||||||
return;
|
logger.info("get fav words", err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleTap = debounce(() => {
|
return [];
|
||||||
translator.toggle();
|
|
||||||
sendIframeMsg(MSG_TRANS_TOGGLE);
|
|
||||||
});
|
|
||||||
touchTapListener(handleTap, touchTranslate);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -214,46 +143,28 @@ export async function run(isUserscript = false) {
|
|||||||
|
|
||||||
// 翻译网页
|
// 翻译网页
|
||||||
const rule = await matchRule(href, setting);
|
const rule = await matchRule(href, setting);
|
||||||
let favWords = [];
|
const favWords = await getFavWords(rule);
|
||||||
if (
|
const fabConfig = await getFabWithDefault();
|
||||||
rule.highlightWords &&
|
const translatorManager = new TranslatorManager({
|
||||||
rule.highlightWords !== OPT_HIGHLIGHT_WORDS_DISABLE
|
|
||||||
) {
|
|
||||||
favWords = Object.keys(await getWordsWithDefault());
|
|
||||||
}
|
|
||||||
const translator = new Translator({
|
|
||||||
rule,
|
|
||||||
setting,
|
setting,
|
||||||
|
rule,
|
||||||
|
fabConfig,
|
||||||
favWords,
|
favWords,
|
||||||
|
isIframe,
|
||||||
isUserscript,
|
isUserscript,
|
||||||
});
|
});
|
||||||
|
translatorManager.start();
|
||||||
|
|
||||||
// 适配iframe
|
|
||||||
if (isIframe) {
|
if (isIframe) {
|
||||||
runIframe(translator);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 字幕翻译
|
// 字幕翻译
|
||||||
runSubtitle({ href, setting, rule, isUserscript });
|
runSubtitle({ href, setting, rule, isUserscript });
|
||||||
|
|
||||||
// 监听消息
|
if (isUserscript) {
|
||||||
// !isUserscript && runtimeListener(translator);
|
trySyncAllSubRules(setting);
|
||||||
|
}
|
||||||
// 输入框翻译
|
|
||||||
// inputTranslate(setting);
|
|
||||||
|
|
||||||
// 划词翻译
|
|
||||||
// showTransbox(setting, rule);
|
|
||||||
|
|
||||||
// 浮球按钮
|
|
||||||
await showFab(translator);
|
|
||||||
|
|
||||||
// 触屏操作
|
|
||||||
touchOperation(translator);
|
|
||||||
|
|
||||||
// 同步订阅规则
|
|
||||||
isUserscript && (await trySyncAllSubRules(setting));
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("[KISS-Translator]", err);
|
console.error("[KISS-Translator]", err);
|
||||||
showErr(err.message);
|
showErr(err.message);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ export const APP_LCNAME = APP_NAME.toLowerCase();
|
|||||||
export const APP_CONSTS = {
|
export const APP_CONSTS = {
|
||||||
fabID: `${APP_LCNAME}-fab`,
|
fabID: `${APP_LCNAME}-fab`,
|
||||||
boxID: `${APP_LCNAME}-box`,
|
boxID: `${APP_LCNAME}-box`,
|
||||||
|
popupID: `${APP_LCNAME}-popup`,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const APP_VERSION = process.env.REACT_APP_VERSION.split(".");
|
export const APP_VERSION = process.env.REACT_APP_VERSION.split(".");
|
||||||
|
|||||||
@@ -1722,4 +1722,4 @@ export const I18N = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const i18n = (lang) => (key) => I18N[key]?.[lang] || "";
|
export const newI18n = (lang) => (key) => I18N[key]?.[lang] || "";
|
||||||
|
|||||||
29
src/hooks/WindowSize.js
Normal file
29
src/hooks/WindowSize.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import { useDebouncedCallback } from "./DebouncedCallback";
|
||||||
|
|
||||||
|
function useWindowSize() {
|
||||||
|
const [windowSize, setWindowSize] = useState({
|
||||||
|
w: window.innerWidth,
|
||||||
|
h: window.innerHeight,
|
||||||
|
});
|
||||||
|
|
||||||
|
const debounceWindowResize = useDebouncedCallback(() => {
|
||||||
|
setWindowSize({
|
||||||
|
w: window.innerWidth,
|
||||||
|
h: window.innerHeight,
|
||||||
|
});
|
||||||
|
}, 200);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
debounceWindowResize();
|
||||||
|
|
||||||
|
window.addEventListener("resize", debounceWindowResize);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("resize", debounceWindowResize);
|
||||||
|
};
|
||||||
|
}, [debounceWindowResize]);
|
||||||
|
|
||||||
|
return windowSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useWindowSize;
|
||||||
14
src/libs/fabManager.js
Normal file
14
src/libs/fabManager.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import ShadowDomManager from "./shadowDomManager";
|
||||||
|
import { APP_CONSTS } from "../config";
|
||||||
|
import ContentFab from "../views/Action/ContentFab";
|
||||||
|
|
||||||
|
export class FabManager extends ShadowDomManager {
|
||||||
|
constructor({ translator, popupManager, fabConfig }) {
|
||||||
|
super({
|
||||||
|
id: APP_CONSTS.fabID,
|
||||||
|
className: "notranslate",
|
||||||
|
reactComponent: ContentFab,
|
||||||
|
props: { translator, popupManager, fabConfig },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/libs/popupManager.js
Normal file
14
src/libs/popupManager.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import ShadowDomManager from "./shadowDomManager";
|
||||||
|
import { APP_CONSTS } from "../config";
|
||||||
|
import Action from "../views/Action";
|
||||||
|
|
||||||
|
export class PopupManager extends ShadowDomManager {
|
||||||
|
constructor({ translator }) {
|
||||||
|
super({
|
||||||
|
id: APP_CONSTS.popupID,
|
||||||
|
className: "notranslate",
|
||||||
|
reactComponent: Action,
|
||||||
|
props: { translator },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
128
src/libs/shadowDomManager.js
Normal file
128
src/libs/shadowDomManager.js
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
import React from "react";
|
||||||
|
import ReactDOM from "react-dom/client";
|
||||||
|
import { CacheProvider } from "@emotion/react";
|
||||||
|
import createCache from "@emotion/cache";
|
||||||
|
import { logger } from "./log";
|
||||||
|
|
||||||
|
export default class ShadowDomManager {
|
||||||
|
#hostElement = null;
|
||||||
|
#reactRoot = null;
|
||||||
|
#isVisible = false;
|
||||||
|
#isProcessing = false;
|
||||||
|
|
||||||
|
_id;
|
||||||
|
_className;
|
||||||
|
_ReactComponent;
|
||||||
|
_props;
|
||||||
|
|
||||||
|
constructor({ id, className = "", reactComponent, props = {} }) {
|
||||||
|
if (!id || !reactComponent) {
|
||||||
|
throw new Error("ID and a React Component must be provided.");
|
||||||
|
}
|
||||||
|
this._id = id;
|
||||||
|
this._className = className;
|
||||||
|
this._ReactComponent = reactComponent;
|
||||||
|
this._props = props;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isVisible() {
|
||||||
|
return this.#isVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
show(props) {
|
||||||
|
if (this.#isVisible || this.#isProcessing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.#hostElement) {
|
||||||
|
this.#isProcessing = true;
|
||||||
|
try {
|
||||||
|
this.#mount(props || this._props);
|
||||||
|
} catch (error) {
|
||||||
|
logger.warn(`Failed to mount component with id "${this._id}":`, error);
|
||||||
|
this.#isProcessing = false;
|
||||||
|
return;
|
||||||
|
} finally {
|
||||||
|
this.#isProcessing = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#hostElement.style.display = "";
|
||||||
|
this.#isVisible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
hide() {
|
||||||
|
if (!this.#isVisible || !this.#hostElement) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.#hostElement.style.display = "none";
|
||||||
|
this.#isVisible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
if (!this.#hostElement) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.#isProcessing = true;
|
||||||
|
|
||||||
|
if (this.#reactRoot) {
|
||||||
|
this.#reactRoot.unmount();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#hostElement.remove();
|
||||||
|
|
||||||
|
this.#hostElement = null;
|
||||||
|
this.#reactRoot = null;
|
||||||
|
this.#isVisible = false;
|
||||||
|
this.#isProcessing = false;
|
||||||
|
logger.info(`Component with id "${this._id}" has been destroyed.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle(props) {
|
||||||
|
if (this.#isVisible) {
|
||||||
|
this.hide();
|
||||||
|
} else {
|
||||||
|
this.show(props || this._props);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#mount(props) {
|
||||||
|
const host = document.createElement("div");
|
||||||
|
host.id = this._id;
|
||||||
|
if (this._className) {
|
||||||
|
host.className = this._className;
|
||||||
|
}
|
||||||
|
host.style.display = "none";
|
||||||
|
document.body.parentElement.appendChild(host);
|
||||||
|
this.#hostElement = host;
|
||||||
|
|
||||||
|
const shadowContainer = host.attachShadow({ mode: "closed" });
|
||||||
|
const emotionRoot = document.createElement("style");
|
||||||
|
const appRoot = document.createElement("div");
|
||||||
|
appRoot.className = `${this._id}_wrapper`;
|
||||||
|
|
||||||
|
shadowContainer.appendChild(emotionRoot);
|
||||||
|
shadowContainer.appendChild(appRoot);
|
||||||
|
|
||||||
|
const cache = createCache({
|
||||||
|
key: this._id,
|
||||||
|
prepend: true,
|
||||||
|
container: emotionRoot,
|
||||||
|
});
|
||||||
|
|
||||||
|
const enhancedProps = {
|
||||||
|
...props,
|
||||||
|
onClose: this.hide.bind(this),
|
||||||
|
};
|
||||||
|
|
||||||
|
const ComponentToRender = this._ReactComponent;
|
||||||
|
this.#reactRoot = ReactDOM.createRoot(appRoot);
|
||||||
|
this.#reactRoot.render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<CacheProvider value={cache}>
|
||||||
|
<ComponentToRender {...enhancedProps} />
|
||||||
|
</CacheProvider>
|
||||||
|
</React.StrictMode>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@ import { kissLog } from "./log";
|
|||||||
* @class ShadowRootMonitor
|
* @class ShadowRootMonitor
|
||||||
* @description 通过覆写 Element.prototype.attachShadow 来监控页面上所有新创建的 Shadow DOM
|
* @description 通过覆写 Element.prototype.attachShadow 来监控页面上所有新创建的 Shadow DOM
|
||||||
*/
|
*/
|
||||||
export class ShadowRootMonitor {
|
export default class ShadowRootMonitor {
|
||||||
/**
|
/**
|
||||||
* @param {function(ShadowRoot): void} callback - 当一个新的 shadowRoot 被创建时调用的回调函数。
|
* @param {function(ShadowRoot): void} callback - 当一个新的 shadowRoot 被创建时调用的回调函数。
|
||||||
*/
|
*/
|
||||||
@@ -10,14 +10,6 @@ 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,
|
|
||||||
OPT_HIGHLIGHT_WORDS_BEFORETRANS,
|
OPT_HIGHLIGHT_WORDS_BEFORETRANS,
|
||||||
OPT_HIGHLIGHT_WORDS_AFTERTRANS,
|
OPT_HIGHLIGHT_WORDS_AFTERTRANS,
|
||||||
OPT_SPLIT_PARAGRAPH_PUNCTUATION,
|
OPT_SPLIT_PARAGRAPH_PUNCTUATION,
|
||||||
@@ -25,7 +17,7 @@ import {
|
|||||||
OPT_SPLIT_PARAGRAPH_TEXTLENGTH,
|
OPT_SPLIT_PARAGRAPH_TEXTLENGTH,
|
||||||
} from "../config";
|
} from "../config";
|
||||||
import interpreter from "./interpreter";
|
import interpreter from "./interpreter";
|
||||||
import { ShadowRootMonitor } from "./shadowroot";
|
import ShadowRootMonitor from "./shadowRootMonitor";
|
||||||
import { clearFetchPool } from "./pool";
|
import { clearFetchPool } from "./pool";
|
||||||
import { debounce, scheduleIdle, genEventName, truncateWords } from "./utils";
|
import { debounce, scheduleIdle, genEventName, truncateWords } from "./utils";
|
||||||
import { apiTranslate } from "../apis";
|
import { apiTranslate } from "../apis";
|
||||||
@@ -38,10 +30,6 @@ import { genTextClass } from "./style";
|
|||||||
import { createLoadingSVG } from "./svg";
|
import { createLoadingSVG } 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";
|
|
||||||
import { trustedTypesHelper } from "./trustedTypes";
|
import { trustedTypesHelper } from "./trustedTypes";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -288,10 +276,6 @@ export class Translator {
|
|||||||
#apisMap = new Map(); // 用于接口快速查找
|
#apisMap = new Map(); // 用于接口快速查找
|
||||||
#favWords = []; // 收藏词汇
|
#favWords = []; // 收藏词汇
|
||||||
|
|
||||||
#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(); // 当前在可视范围内的单元
|
||||||
@@ -339,12 +323,7 @@ export class Translator {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor({
|
constructor({ rule = {}, setting = {}, favWords = [] }) {
|
||||||
rule = {},
|
|
||||||
setting = {},
|
|
||||||
favWords = [],
|
|
||||||
isUserscript = false,
|
|
||||||
}) {
|
|
||||||
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.#favWords = favWords;
|
this.#favWords = favWords;
|
||||||
@@ -352,7 +331,6 @@ export class Translator {
|
|||||||
this.#setting.transApis.map((api) => [api.apiSlug, api])
|
this.#setting.transApis.map((api) => [api.apiSlug, api])
|
||||||
);
|
);
|
||||||
|
|
||||||
this.#isUserscript = isUserscript;
|
|
||||||
this.#eventName = genEventName();
|
this.#eventName = genEventName();
|
||||||
this.#docInfo = {
|
this.#docInfo = {
|
||||||
title: document.title,
|
title: document.title,
|
||||||
@@ -384,19 +362,6 @@ 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 {
|
||||||
@@ -439,43 +404,6 @@ 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(
|
||||||
this.#placeholder.startDelimiter
|
this.#placeholder.startDelimiter
|
||||||
@@ -1716,19 +1644,6 @@ 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();
|
||||||
|
|||||||
258
src/libs/translatorManager.js
Normal file
258
src/libs/translatorManager.js
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
import { browser } from "./browser";
|
||||||
|
import { Translator } from "./translator";
|
||||||
|
import { InputTranslator } from "./inputTranslate";
|
||||||
|
import { TransboxManager } from "./tranbox";
|
||||||
|
import { shortcutRegister } from "./shortcut";
|
||||||
|
import { sendIframeMsg } from "./iframe";
|
||||||
|
import { newI18n } from "../config";
|
||||||
|
import { touchTapListener } from "./touch";
|
||||||
|
import { debounce } from "./utils";
|
||||||
|
import { PopupManager } from "./popupManager";
|
||||||
|
import { FabManager } from "./fabManager";
|
||||||
|
import {
|
||||||
|
OPT_SHORTCUT_TRANSLATE,
|
||||||
|
OPT_SHORTCUT_STYLE,
|
||||||
|
OPT_SHORTCUT_POPUP,
|
||||||
|
OPT_SHORTCUT_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";
|
||||||
|
import { logger } from "./log";
|
||||||
|
|
||||||
|
export default class TranslatorManager {
|
||||||
|
#clearShortcuts = [];
|
||||||
|
#menuCommandIds = [];
|
||||||
|
#clearTouchListener = null;
|
||||||
|
#isActive = false;
|
||||||
|
#isUserscript;
|
||||||
|
#isIframe;
|
||||||
|
|
||||||
|
#windowMessageHandler = null;
|
||||||
|
#browserMessageHandler = null;
|
||||||
|
|
||||||
|
_translator;
|
||||||
|
_transboxManager;
|
||||||
|
_inputTranslator;
|
||||||
|
_popupManager;
|
||||||
|
_fabManager;
|
||||||
|
|
||||||
|
constructor({ setting, rule, fabConfig, favWords, isIframe, isUserscript }) {
|
||||||
|
this.#isIframe = isIframe;
|
||||||
|
this.#isUserscript = isUserscript;
|
||||||
|
|
||||||
|
this._translator = new Translator({
|
||||||
|
rule,
|
||||||
|
setting,
|
||||||
|
favWords,
|
||||||
|
isUserscript,
|
||||||
|
isIframe,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!isIframe) {
|
||||||
|
this._transboxManager = new TransboxManager(setting);
|
||||||
|
this._inputTranslator = new InputTranslator(setting);
|
||||||
|
this._popupManager = new PopupManager({ translator: this._translator });
|
||||||
|
|
||||||
|
if (fabConfig && !fabConfig.isHide) {
|
||||||
|
this._fabManager = new FabManager({
|
||||||
|
translator: this._translator,
|
||||||
|
popupManager: this._popupManager,
|
||||||
|
fabConfig,
|
||||||
|
});
|
||||||
|
this._fabManager.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#windowMessageHandler = this.#handleWindowMessage.bind(this);
|
||||||
|
this.#browserMessageHandler = this.#handleBrowserMessage.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
if (this.#isActive) {
|
||||||
|
logger.info("TranslatorManager is already started.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#setupMessageListeners();
|
||||||
|
this.#setupTouchOperations();
|
||||||
|
|
||||||
|
if (!this.#isIframe && this.#isUserscript) {
|
||||||
|
this.#registerShortcuts();
|
||||||
|
this.#registerMenus();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#isActive = true;
|
||||||
|
logger.info("TranslatorManager started.");
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
if (!this.#isActive) {
|
||||||
|
logger.info("TranslatorManager is not running.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除消息监听器
|
||||||
|
if (this.#isUserscript) {
|
||||||
|
window.removeEventListener("message", this.#windowMessageHandler);
|
||||||
|
} else if (
|
||||||
|
browser.runtime.onMessage.hasListener(this.#browserMessageHandler)
|
||||||
|
) {
|
||||||
|
browser.runtime.onMessage.removeListener(this.#browserMessageHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 已注册的快捷键
|
||||||
|
this.#clearShortcuts.forEach((clear) => clear());
|
||||||
|
this.#clearShortcuts = [];
|
||||||
|
|
||||||
|
// 触屏
|
||||||
|
if (this.#clearTouchListener) {
|
||||||
|
this.#clearTouchListener();
|
||||||
|
this.#clearTouchListener = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 油猴菜单
|
||||||
|
if (globalThis.GM && this.#menuCommandIds.length > 0) {
|
||||||
|
this.#menuCommandIds.forEach((id) =>
|
||||||
|
globalThis.GM.unregisterMenuCommand(id)
|
||||||
|
);
|
||||||
|
this.#menuCommandIds = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 子模块
|
||||||
|
this._popupManager?.hide();
|
||||||
|
this._fabManager?.hide();
|
||||||
|
this._transboxManager?.disable();
|
||||||
|
this._inputTranslator?.disable();
|
||||||
|
this._translator.stop();
|
||||||
|
|
||||||
|
this.#isActive = false;
|
||||||
|
logger.info("TranslatorManager stopped.");
|
||||||
|
}
|
||||||
|
|
||||||
|
#setupMessageListeners() {
|
||||||
|
if (this.#isUserscript) {
|
||||||
|
window.addEventListener("message", this.#windowMessageHandler);
|
||||||
|
} else {
|
||||||
|
browser.runtime.onMessage.addListener(this.#browserMessageHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#setupTouchOperations() {
|
||||||
|
if (this.#isIframe) return;
|
||||||
|
|
||||||
|
const { touchTranslate = 2 } = this._translator.setting;
|
||||||
|
if (touchTranslate === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleTap = debounce(() => {
|
||||||
|
this.#processActions({ action: MSG_TRANS_TOGGLE });
|
||||||
|
}, 300);
|
||||||
|
|
||||||
|
this.#clearTouchListener = touchTapListener(handleTap, touchTranslate);
|
||||||
|
}
|
||||||
|
|
||||||
|
#handleWindowMessage(event) {
|
||||||
|
this.#processActions(event.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
#handleBrowserMessage(message, sender, sendResponse) {
|
||||||
|
const result = this.#processActions(message);
|
||||||
|
const response = result || {
|
||||||
|
rule: this._translator.rule,
|
||||||
|
setting: this._translator.setting,
|
||||||
|
};
|
||||||
|
sendResponse(response);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#registerShortcuts() {
|
||||||
|
const { shortcuts } = this._translator.setting;
|
||||||
|
this.#clearShortcuts = [
|
||||||
|
shortcutRegister(shortcuts[OPT_SHORTCUT_TRANSLATE], () =>
|
||||||
|
this.#processActions({ action: MSG_TRANS_TOGGLE })
|
||||||
|
),
|
||||||
|
shortcutRegister(shortcuts[OPT_SHORTCUT_STYLE], () =>
|
||||||
|
this.#processActions({ action: MSG_TRANS_TOGGLE_STYLE })
|
||||||
|
),
|
||||||
|
shortcutRegister(shortcuts[OPT_SHORTCUT_POPUP], () =>
|
||||||
|
this._popupManager.toggle()
|
||||||
|
),
|
||||||
|
shortcutRegister(shortcuts[OPT_SHORTCUT_SETTING], () =>
|
||||||
|
window.open(process.env.REACT_APP_OPTIONSPAGE, "_blank")
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
#registerMenus() {
|
||||||
|
if (!globalThis.GM) return;
|
||||||
|
const { contextMenuType, uiLang } = this._translator.setting;
|
||||||
|
if (contextMenuType === 0) return;
|
||||||
|
|
||||||
|
const i18n = newI18n(uiLang || "zh");
|
||||||
|
const GM = globalThis.GM;
|
||||||
|
this.#menuCommandIds = [
|
||||||
|
GM.registerMenuCommand(
|
||||||
|
i18n("translate_switch"),
|
||||||
|
() => this.#processActions({ action: MSG_TRANS_TOGGLE }),
|
||||||
|
"Q"
|
||||||
|
),
|
||||||
|
GM.registerMenuCommand(
|
||||||
|
i18n("toggle_style"),
|
||||||
|
() => this.#processActions({ action: MSG_TRANS_TOGGLE_STYLE }),
|
||||||
|
"C"
|
||||||
|
),
|
||||||
|
GM.registerMenuCommand(
|
||||||
|
i18n("open_menu"),
|
||||||
|
() => this._popupManager.toggle(),
|
||||||
|
"K"
|
||||||
|
),
|
||||||
|
GM.registerMenuCommand(
|
||||||
|
i18n("open_setting"),
|
||||||
|
() => window.open(process.env.REACT_APP_OPTIONSPAGE, "_blank"),
|
||||||
|
"O"
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
#processActions({ action, args } = {}) {
|
||||||
|
if (this.#isUserscript) {
|
||||||
|
sendIframeMsg(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
case MSG_TRANS_TOGGLE:
|
||||||
|
this._translator.toggle();
|
||||||
|
break;
|
||||||
|
case MSG_TRANS_TOGGLE_STYLE:
|
||||||
|
this._translator.toggleStyle();
|
||||||
|
break;
|
||||||
|
case MSG_TRANS_GETRULE:
|
||||||
|
break;
|
||||||
|
case MSG_TRANS_PUTRULE:
|
||||||
|
this._translator.updateRule(args);
|
||||||
|
break;
|
||||||
|
case MSG_OPEN_TRANBOX:
|
||||||
|
this._transboxManager?.enable();
|
||||||
|
break;
|
||||||
|
case MSG_TRANSBOX_TOGGLE:
|
||||||
|
this._transboxManager?.toggle();
|
||||||
|
break;
|
||||||
|
case MSG_MOUSEHOVER_TOGGLE:
|
||||||
|
this._translator.toggleMouseHover();
|
||||||
|
break;
|
||||||
|
case MSG_TRANSINPUT_TOGGLE:
|
||||||
|
this._inputTranslator?.toggle();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
logger.info(`Message action is unavailable: ${action}`);
|
||||||
|
return { error: `Message action is unavailable: ${action}` };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { logger } from "./log";
|
||||||
|
|
||||||
export const trustedTypesHelper = (() => {
|
export const trustedTypesHelper = (() => {
|
||||||
const POLICY_NAME = "kiss-translator-policy";
|
const POLICY_NAME = "kiss-translator-policy";
|
||||||
let policy = null;
|
let policy = null;
|
||||||
@@ -13,7 +15,7 @@ export const trustedTypesHelper = (() => {
|
|||||||
if (err.message.includes("already exists")) {
|
if (err.message.includes("already exists")) {
|
||||||
policy = globalThis.trustedTypes.policies.get(POLICY_NAME);
|
policy = globalThis.trustedTypes.policies.get(POLICY_NAME);
|
||||||
} else {
|
} else {
|
||||||
console.error("cont create Trusted Types", err);
|
logger.info("cont create Trusted Types", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
import { sleep } from "../libs/utils.js";
|
import { sleep } from "../libs/utils.js";
|
||||||
import { createLogoSVG } from "../libs/svg.js";
|
import { createLogoSVG } from "../libs/svg.js";
|
||||||
import { randomBetween } from "../libs/utils.js";
|
import { randomBetween } from "../libs/utils.js";
|
||||||
import { i18n } from "../config";
|
import { newI18n } from "../config";
|
||||||
|
|
||||||
const VIDEO_SELECT = "#container video";
|
const VIDEO_SELECT = "#container video";
|
||||||
const CONTORLS_SELECT = ".ytp-right-controls";
|
const CONTORLS_SELECT = ".ytp-right-controls";
|
||||||
@@ -33,7 +33,7 @@ class YouTubeCaptionProvider {
|
|||||||
|
|
||||||
constructor(setting = {}) {
|
constructor(setting = {}) {
|
||||||
this.#setting = setting;
|
this.#setting = setting;
|
||||||
this.#i18n = i18n(setting.uiLang || "zh");
|
this.#i18n = newI18n(setting.uiLang || "zh");
|
||||||
}
|
}
|
||||||
|
|
||||||
initialize() {
|
initialize() {
|
||||||
|
|||||||
73
src/views/Action/ContentFab.js
Normal file
73
src/views/Action/ContentFab.js
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import Fab from "@mui/material/Fab";
|
||||||
|
import TranslateIcon from "@mui/icons-material/Translate";
|
||||||
|
import ThemeProvider from "../../hooks/Theme";
|
||||||
|
import Draggable from "./Draggable";
|
||||||
|
import { useState, useMemo, useCallback } from "react";
|
||||||
|
import { SettingProvider } from "../../hooks/Setting";
|
||||||
|
import { MSG_TRANS_TOGGLE } from "../../config";
|
||||||
|
import { sendIframeMsg } from "../../libs/iframe";
|
||||||
|
import useWindowSize from "../../hooks/WindowSize";
|
||||||
|
|
||||||
|
export default function ContentFab({
|
||||||
|
translator,
|
||||||
|
fabConfig: { x: fabX, y: fabY, fabClickAction = 0 } = {},
|
||||||
|
popupManager,
|
||||||
|
}) {
|
||||||
|
const fabWidth = 40;
|
||||||
|
const windowSize = useWindowSize();
|
||||||
|
const [moved, setMoved] = useState(false);
|
||||||
|
|
||||||
|
const handleStart = useCallback(() => {
|
||||||
|
setMoved(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleMove = useCallback(() => {
|
||||||
|
setMoved(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleClick = useCallback(() => {
|
||||||
|
if (!moved) {
|
||||||
|
if (fabClickAction === 1) {
|
||||||
|
translator.toggle();
|
||||||
|
sendIframeMsg(MSG_TRANS_TOGGLE);
|
||||||
|
} else {
|
||||||
|
popupManager.toggle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [moved, translator, popupManager, fabClickAction]);
|
||||||
|
|
||||||
|
const fabProps = useMemo(
|
||||||
|
() => ({
|
||||||
|
windowSize,
|
||||||
|
width: fabWidth,
|
||||||
|
height: fabWidth,
|
||||||
|
left: fabX ?? -fabWidth,
|
||||||
|
top: fabY ?? windowSize.h / 2,
|
||||||
|
}),
|
||||||
|
[windowSize, fabWidth, fabX, fabY]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SettingProvider>
|
||||||
|
<ThemeProvider>
|
||||||
|
<Draggable
|
||||||
|
key="fab"
|
||||||
|
snapEdge
|
||||||
|
{...fabProps}
|
||||||
|
onStart={handleStart}
|
||||||
|
onMove={handleMove}
|
||||||
|
handler={
|
||||||
|
<Fab size="small" color="primary" onClick={handleClick}>
|
||||||
|
<TranslateIcon
|
||||||
|
sx={{
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Fab>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</ThemeProvider>
|
||||||
|
</SettingProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -50,7 +50,7 @@ export default function Draggable({
|
|||||||
height,
|
height,
|
||||||
left,
|
left,
|
||||||
top,
|
top,
|
||||||
show,
|
show = true,
|
||||||
snapEdge,
|
snapEdge,
|
||||||
onStart,
|
onStart,
|
||||||
onMove,
|
onMove,
|
||||||
|
|||||||
@@ -1,157 +1,19 @@
|
|||||||
import Fab from "@mui/material/Fab";
|
|
||||||
import TranslateIcon from "@mui/icons-material/Translate";
|
|
||||||
import ThemeProvider from "../../hooks/Theme";
|
import ThemeProvider from "../../hooks/Theme";
|
||||||
import Draggable from "./Draggable";
|
import Draggable from "./Draggable";
|
||||||
import { useEffect, useState, useMemo, useCallback } from "react";
|
import { useEffect, useMemo, useCallback } from "react";
|
||||||
import { SettingProvider } from "../../hooks/Setting";
|
import { SettingProvider } from "../../hooks/Setting";
|
||||||
import Popup from "../Popup";
|
import Popup from "../Popup";
|
||||||
import { debounce } from "../../libs/utils";
|
|
||||||
import { isGm } from "../../libs/client";
|
|
||||||
import Header from "../Popup/Header";
|
import Header from "../Popup/Header";
|
||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
import Divider from "@mui/material/Divider";
|
import Divider from "@mui/material/Divider";
|
||||||
import {
|
import useWindowSize from "../../hooks/WindowSize";
|
||||||
DEFAULT_SHORTCUTS,
|
|
||||||
OPT_SHORTCUT_TRANSLATE,
|
|
||||||
OPT_SHORTCUT_STYLE,
|
|
||||||
OPT_SHORTCUT_POPUP,
|
|
||||||
OPT_SHORTCUT_SETTING,
|
|
||||||
MSG_TRANS_TOGGLE,
|
|
||||||
MSG_TRANS_TOGGLE_STYLE,
|
|
||||||
} from "../../config";
|
|
||||||
import { shortcutRegister } from "../../libs/shortcut";
|
|
||||||
import { sendIframeMsg } from "../../libs/iframe";
|
|
||||||
import { kissLog } from "../../libs/log";
|
|
||||||
import { getI18n } from "../../hooks/I18n";
|
|
||||||
|
|
||||||
export default function Action({ translator, fab }) {
|
export default function Action({ translator, onClose }) {
|
||||||
const fabWidth = 40;
|
const windowSize = useWindowSize();
|
||||||
const [showPopup, setShowPopup] = useState(false);
|
|
||||||
const [windowSize, setWindowSize] = useState({
|
|
||||||
w: window.innerWidth,
|
|
||||||
h: window.innerHeight,
|
|
||||||
});
|
|
||||||
const [moved, setMoved] = useState(false);
|
|
||||||
|
|
||||||
const { fabClickAction = 0 } = fab || {};
|
const handleWindowClick = useCallback(() => {
|
||||||
|
onClose();
|
||||||
const handleWindowResize = useMemo(
|
}, [onClose]);
|
||||||
() =>
|
|
||||||
debounce(() => {
|
|
||||||
setWindowSize({
|
|
||||||
w: window.innerWidth,
|
|
||||||
h: window.innerHeight,
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleWindowClick = (e) => {
|
|
||||||
setShowPopup(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleStart = useCallback(() => {
|
|
||||||
setMoved(false);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleMove = useCallback(() => {
|
|
||||||
setMoved(true);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isGm) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 注册快捷键
|
|
||||||
const shortcuts = translator.setting.shortcuts || DEFAULT_SHORTCUTS;
|
|
||||||
const clearShortcuts = [
|
|
||||||
shortcutRegister(shortcuts[OPT_SHORTCUT_TRANSLATE], () => {
|
|
||||||
translator.toggle();
|
|
||||||
sendIframeMsg(MSG_TRANS_TOGGLE);
|
|
||||||
setShowPopup(false);
|
|
||||||
}),
|
|
||||||
shortcutRegister(shortcuts[OPT_SHORTCUT_STYLE], () => {
|
|
||||||
translator.toggleStyle();
|
|
||||||
sendIframeMsg(MSG_TRANS_TOGGLE_STYLE);
|
|
||||||
setShowPopup(false);
|
|
||||||
}),
|
|
||||||
shortcutRegister(shortcuts[OPT_SHORTCUT_POPUP], () => {
|
|
||||||
setShowPopup((pre) => !pre);
|
|
||||||
}),
|
|
||||||
shortcutRegister(shortcuts[OPT_SHORTCUT_SETTING], () => {
|
|
||||||
window.open(process.env.REACT_APP_OPTIONSPAGE, "_blank");
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
clearShortcuts.forEach((fn) => {
|
|
||||||
fn();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}, [translator]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isGm) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 注册菜单
|
|
||||||
try {
|
|
||||||
const menuCommandIds = [];
|
|
||||||
const { contextMenuType, uiLang } = translator.setting;
|
|
||||||
contextMenuType !== 0 &&
|
|
||||||
menuCommandIds.push(
|
|
||||||
GM.registerMenuCommand(
|
|
||||||
getI18n(uiLang, "translate_switch"),
|
|
||||||
(event) => {
|
|
||||||
translator.toggle();
|
|
||||||
sendIframeMsg(MSG_TRANS_TOGGLE);
|
|
||||||
setShowPopup(false);
|
|
||||||
},
|
|
||||||
"Q"
|
|
||||||
),
|
|
||||||
GM.registerMenuCommand(
|
|
||||||
getI18n(uiLang, "toggle_style"),
|
|
||||||
(event) => {
|
|
||||||
translator.toggleStyle();
|
|
||||||
sendIframeMsg(MSG_TRANS_TOGGLE_STYLE);
|
|
||||||
setShowPopup(false);
|
|
||||||
},
|
|
||||||
"C"
|
|
||||||
),
|
|
||||||
GM.registerMenuCommand(
|
|
||||||
getI18n(uiLang, "open_menu"),
|
|
||||||
(event) => {
|
|
||||||
setShowPopup((pre) => !pre);
|
|
||||||
},
|
|
||||||
"K"
|
|
||||||
),
|
|
||||||
GM.registerMenuCommand(
|
|
||||||
getI18n(uiLang, "open_setting"),
|
|
||||||
(event) => {
|
|
||||||
window.open(process.env.REACT_APP_OPTIONSPAGE, "_blank");
|
|
||||||
},
|
|
||||||
"O"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
menuCommandIds.forEach((id) => {
|
|
||||||
GM.unregisterMenuCommand(id);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
} catch (err) {
|
|
||||||
kissLog("registerMenuCommand", err);
|
|
||||||
}
|
|
||||||
}, [translator]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
window.addEventListener("resize", handleWindowResize);
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener("resize", handleWindowResize);
|
|
||||||
};
|
|
||||||
}, [handleWindowResize]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.addEventListener("click", handleWindowClick);
|
window.addEventListener("click", handleWindowClick);
|
||||||
@@ -159,7 +21,7 @@ export default function Action({ translator, fab }) {
|
|||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener("click", handleWindowClick);
|
window.removeEventListener("click", handleWindowClick);
|
||||||
};
|
};
|
||||||
}, []);
|
}, [handleWindowClick]);
|
||||||
|
|
||||||
const popProps = useMemo(() => {
|
const popProps = useMemo(() => {
|
||||||
const width = Math.min(windowSize.w, 360);
|
const width = Math.min(windowSize.w, 360);
|
||||||
@@ -175,67 +37,22 @@ export default function Action({ translator, fab }) {
|
|||||||
};
|
};
|
||||||
}, [windowSize]);
|
}, [windowSize]);
|
||||||
|
|
||||||
const fabProps = {
|
|
||||||
windowSize,
|
|
||||||
width: fabWidth,
|
|
||||||
height: fabWidth,
|
|
||||||
left: fab.x ?? -fabWidth,
|
|
||||||
top: fab.y ?? windowSize.h / 2,
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingProvider>
|
<SettingProvider>
|
||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
<Draggable
|
<Draggable
|
||||||
key="pop"
|
key="pop"
|
||||||
{...popProps}
|
{...popProps}
|
||||||
show={showPopup}
|
|
||||||
onStart={handleStart}
|
|
||||||
onMove={handleMove}
|
|
||||||
usePaper
|
usePaper
|
||||||
handler={
|
handler={
|
||||||
<Box style={{ cursor: "move" }}>
|
<Box style={{ cursor: "move" }}>
|
||||||
<Header setShowPopup={setShowPopup} />
|
<Header onClose={onClose} />
|
||||||
<Divider />
|
<Divider />
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{showPopup && (
|
<Popup translator={translator} />
|
||||||
<Popup setShowPopup={setShowPopup} translator={translator} />
|
|
||||||
)}
|
|
||||||
</Draggable>
|
</Draggable>
|
||||||
<Draggable
|
|
||||||
key="fab"
|
|
||||||
snapEdge
|
|
||||||
{...fabProps}
|
|
||||||
show={fab.isHide ? false : !showPopup}
|
|
||||||
onStart={handleStart}
|
|
||||||
onMove={handleMove}
|
|
||||||
handler={
|
|
||||||
<Fab
|
|
||||||
size="small"
|
|
||||||
color="primary"
|
|
||||||
onClick={(e) => {
|
|
||||||
if (!moved) {
|
|
||||||
if (fabClickAction === 1) {
|
|
||||||
translator.toggle();
|
|
||||||
sendIframeMsg(MSG_TRANS_TOGGLE);
|
|
||||||
setShowPopup(false);
|
|
||||||
} else {
|
|
||||||
setShowPopup((pre) => !pre);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<TranslateIcon
|
|
||||||
sx={{
|
|
||||||
width: 24,
|
|
||||||
height: 24,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Fab>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</SettingProvider>
|
</SettingProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import Stack from "@mui/material/Stack";
|
|||||||
import DarkModeButton from "../Options/DarkModeButton";
|
import DarkModeButton from "../Options/DarkModeButton";
|
||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
|
|
||||||
export default function Header({ setShowPopup }) {
|
export default function Header({ onClose }) {
|
||||||
const handleHomepage = () => {
|
const handleHomepage = () => {
|
||||||
window.open(process.env.REACT_APP_HOMEPAGE, "_blank");
|
window.open(process.env.REACT_APP_HOMEPAGE, "_blank");
|
||||||
};
|
};
|
||||||
@@ -33,10 +33,10 @@ export default function Header({ setShowPopup }) {
|
|||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
{setShowPopup ? (
|
{onClose ? (
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowPopup(false);
|
onClose();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CloseIcon />
|
<CloseIcon />
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ import { parseUrlPattern } from "../../libs/utils";
|
|||||||
|
|
||||||
// 插件popup没有参数
|
// 插件popup没有参数
|
||||||
// 网页弹框有
|
// 网页弹框有
|
||||||
export default function Popup({ setShowPopup, translator }) {
|
export default function Popup({ translator }) {
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const [rule, setRule] = useState(translator?.rule);
|
const [rule, setRule] = useState(translator?.rule);
|
||||||
const [setting, setSetting] = useState(translator?.setting);
|
const [setting, setSetting] = useState(translator?.setting);
|
||||||
@@ -49,7 +49,6 @@ export default function Popup({ setShowPopup, translator }) {
|
|||||||
} else {
|
} else {
|
||||||
window.open(process.env.REACT_APP_OPTIONSPAGE, "_blank");
|
window.open(process.env.REACT_APP_OPTIONSPAGE, "_blank");
|
||||||
}
|
}
|
||||||
setShowPopup && setShowPopup(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTransToggle = async (e) => {
|
const handleTransToggle = async (e) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user