userscript...
This commit is contained in:
2
.env
2
.env
@@ -1,3 +1,5 @@
|
|||||||
GENERATE_SOURCEMAP=false
|
GENERATE_SOURCEMAP=false
|
||||||
|
|
||||||
REACT_APP_NAME=KISS Translator
|
REACT_APP_NAME=KISS Translator
|
||||||
|
REACT_APP_VERSION=1.2.3
|
||||||
|
REACT_APP_HOMEPAGE=https://github.com/fishjar/kiss-translator
|
||||||
|
|||||||
@@ -2,74 +2,138 @@ const paths = require("react-scripts/config/paths");
|
|||||||
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||||
const { WebpackManifestPlugin } = require("webpack-manifest-plugin");
|
const { WebpackManifestPlugin } = require("webpack-manifest-plugin");
|
||||||
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
||||||
|
const webpack = require("webpack");
|
||||||
|
|
||||||
|
const isEnvUserscript = process.env.REACT_APP_CLIENT === "userscript";
|
||||||
|
|
||||||
|
const banner = `// ==UserScript==
|
||||||
|
// @name ${process.env.REACT_APP_NAME}
|
||||||
|
// @namespace ${process.env.REACT_APP_HOMEPAGE}
|
||||||
|
// @version ${process.env.REACT_APP_VERSION}
|
||||||
|
// @description A minimalist bilingual translation extension.
|
||||||
|
// @author Gabe<yugang2002@gmail.com>
|
||||||
|
// @homepageURL ${process.env.REACT_APP_HOMEPAGE}
|
||||||
|
// @match *://*/*
|
||||||
|
// @icon https://raw.githubusercontent.com/fishjar/kiss-translator/master/public/images/logo192.png
|
||||||
|
// @downloadURL https://raw.githubusercontent.com/fishjar/kiss-translator/master/dist/userscript/kiss-translator.user.js
|
||||||
|
// @updateURL https://raw.githubusercontent.com/fishjar/kiss-translator/master/dist/userscript/kiss-translator.user.js
|
||||||
|
// @grant GM_xmlhttpRequest
|
||||||
|
// @grant GM.xmlhttpRequest
|
||||||
|
// @grant GM_setValue
|
||||||
|
// @grant GM_getValue
|
||||||
|
// @grant GM.setValue
|
||||||
|
// @grant GM.getValue
|
||||||
|
// @connect translate.googleapis.com
|
||||||
|
// @connect api-edge.cognitive.microsofttranslator.com
|
||||||
|
// @connect edge.microsoft.com
|
||||||
|
// @connect api.openai.com
|
||||||
|
// @connect localhost
|
||||||
|
// ==/UserScript==
|
||||||
|
|
||||||
|
`;
|
||||||
|
|
||||||
|
// 扩展
|
||||||
|
const extWebpack = (config, env) => {
|
||||||
|
const isEnvProduction = env === "production";
|
||||||
|
const minify = isEnvProduction && {
|
||||||
|
removeComments: true,
|
||||||
|
collapseWhitespace: true,
|
||||||
|
removeRedundantAttributes: true,
|
||||||
|
useShortDoctype: true,
|
||||||
|
removeEmptyAttributes: true,
|
||||||
|
removeStyleLinkTypeAttributes: true,
|
||||||
|
keepClosingSlash: true,
|
||||||
|
minifyJS: true,
|
||||||
|
minifyCSS: true,
|
||||||
|
minifyURLs: true,
|
||||||
|
};
|
||||||
|
const names = [
|
||||||
|
"HtmlWebpackPlugin",
|
||||||
|
"WebpackManifestPlugin",
|
||||||
|
"MiniCssExtractPlugin",
|
||||||
|
];
|
||||||
|
|
||||||
|
config.entry = {
|
||||||
|
popup: paths.appIndexJs,
|
||||||
|
options: paths.appSrc + "/options.js",
|
||||||
|
background: paths.appSrc + "/background.js",
|
||||||
|
content: paths.appSrc + "/content.js",
|
||||||
|
};
|
||||||
|
|
||||||
|
config.output.filename = "[name].js";
|
||||||
|
config.output.assetModuleFilename = "media/[name][ext]";
|
||||||
|
config.optimization.splitChunks = { cacheGroups: { default: false } };
|
||||||
|
config.optimization.runtimeChunk = false;
|
||||||
|
|
||||||
|
config.plugins = config.plugins.filter(
|
||||||
|
(plugin) => !names.includes(plugin.constructor.name)
|
||||||
|
);
|
||||||
|
|
||||||
|
config.plugins.push(
|
||||||
|
new HtmlWebpackPlugin({
|
||||||
|
inject: true,
|
||||||
|
chunks: ["options"],
|
||||||
|
template: paths.appHtml,
|
||||||
|
filename: "options.html",
|
||||||
|
minify,
|
||||||
|
}),
|
||||||
|
new HtmlWebpackPlugin({
|
||||||
|
inject: true,
|
||||||
|
chunks: ["content"],
|
||||||
|
template: paths.appPublic + "/content.html",
|
||||||
|
filename: "content.html",
|
||||||
|
minify,
|
||||||
|
}),
|
||||||
|
new HtmlWebpackPlugin({
|
||||||
|
inject: true,
|
||||||
|
chunks: ["popup"],
|
||||||
|
template: paths.appHtml,
|
||||||
|
filename: "popup.html",
|
||||||
|
minify,
|
||||||
|
}),
|
||||||
|
new WebpackManifestPlugin({
|
||||||
|
fileName: "asset-manifest.json",
|
||||||
|
}),
|
||||||
|
new MiniCssExtractPlugin({
|
||||||
|
filename: "css/[name].css",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return config;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 油猴
|
||||||
|
const userscriptWebpack = (config, env) => {
|
||||||
|
const names = [
|
||||||
|
"HtmlWebpackPlugin",
|
||||||
|
"WebpackManifestPlugin",
|
||||||
|
"MiniCssExtractPlugin",
|
||||||
|
];
|
||||||
|
|
||||||
|
config.entry = {
|
||||||
|
userscript: paths.appSrc + "/userscript.js",
|
||||||
|
};
|
||||||
|
|
||||||
|
config.output.filename = "kiss-translator.user.js";
|
||||||
|
config.optimization.splitChunks = { cacheGroups: { default: false } };
|
||||||
|
config.optimization.runtimeChunk = false;
|
||||||
|
config.optimization.minimize = false;
|
||||||
|
|
||||||
|
config.plugins = config.plugins.filter(
|
||||||
|
(plugin) => !names.includes(plugin.constructor.name)
|
||||||
|
);
|
||||||
|
|
||||||
|
config.plugins.push(
|
||||||
|
new webpack.BannerPlugin({
|
||||||
|
banner,
|
||||||
|
raw: true,
|
||||||
|
entryOnly: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return config;
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
webpack: (config, env) => {
|
webpack: isEnvUserscript ? userscriptWebpack : extWebpack,
|
||||||
const isEnvProduction = env === "production";
|
|
||||||
const minify = isEnvProduction && {
|
|
||||||
removeComments: true,
|
|
||||||
collapseWhitespace: true,
|
|
||||||
removeRedundantAttributes: true,
|
|
||||||
useShortDoctype: true,
|
|
||||||
removeEmptyAttributes: true,
|
|
||||||
removeStyleLinkTypeAttributes: true,
|
|
||||||
keepClosingSlash: true,
|
|
||||||
minifyJS: true,
|
|
||||||
minifyCSS: true,
|
|
||||||
minifyURLs: true,
|
|
||||||
};
|
|
||||||
const names = [
|
|
||||||
"HtmlWebpackPlugin",
|
|
||||||
"WebpackManifestPlugin",
|
|
||||||
"MiniCssExtractPlugin",
|
|
||||||
];
|
|
||||||
|
|
||||||
config.entry = {
|
|
||||||
popup: paths.appIndexJs,
|
|
||||||
options: paths.appSrc + "/options.js",
|
|
||||||
background: paths.appSrc + "/background.js",
|
|
||||||
content: paths.appSrc + "/content.js",
|
|
||||||
};
|
|
||||||
|
|
||||||
config.output.filename = "[name].js";
|
|
||||||
config.output.assetModuleFilename = "media/[name][ext]";
|
|
||||||
config.optimization.splitChunks = { cacheGroups: { default: false } };
|
|
||||||
config.optimization.runtimeChunk = false;
|
|
||||||
|
|
||||||
config.plugins = config.plugins.filter(
|
|
||||||
(plugin) => !names.includes(plugin.constructor.name)
|
|
||||||
);
|
|
||||||
|
|
||||||
config.plugins.push(
|
|
||||||
new HtmlWebpackPlugin({
|
|
||||||
inject: true,
|
|
||||||
chunks: ["options"],
|
|
||||||
template: paths.appHtml,
|
|
||||||
filename: "options.html",
|
|
||||||
minify,
|
|
||||||
}),
|
|
||||||
new HtmlWebpackPlugin({
|
|
||||||
inject: true,
|
|
||||||
chunks: ["content"],
|
|
||||||
template: paths.appPublic + "/content.html",
|
|
||||||
filename: "content.html",
|
|
||||||
minify,
|
|
||||||
}),
|
|
||||||
new HtmlWebpackPlugin({
|
|
||||||
inject: true,
|
|
||||||
chunks: ["popup"],
|
|
||||||
template: paths.appHtml,
|
|
||||||
filename: "popup.html",
|
|
||||||
minify,
|
|
||||||
}),
|
|
||||||
new WebpackManifestPlugin({
|
|
||||||
fileName: "asset-manifest.json",
|
|
||||||
}),
|
|
||||||
new MiniCssExtractPlugin({
|
|
||||||
filename: "css/[name].css",
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
return config;
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -19,9 +19,11 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "REACT_APP_CLIENT=web react-app-rewired start",
|
"start": "REACT_APP_CLIENT=web react-app-rewired start",
|
||||||
|
"start:userscript": "REACT_APP_CLIENT=userscript react-app-rewired start",
|
||||||
"build": "BUILD_PATH=./build/chrome REACT_APP_CLIENT=chrome react-app-rewired build && rm ./build/chrome/manifest.firefox.json",
|
"build": "BUILD_PATH=./build/chrome REACT_APP_CLIENT=chrome react-app-rewired build && rm ./build/chrome/manifest.firefox.json",
|
||||||
"build:edge": "BUILD_PATH=./build/edge REACT_APP_CLIENT=edge react-app-rewired build && rm ./build/edge/manifest.firefox.json",
|
"build:edge": "BUILD_PATH=./build/edge REACT_APP_CLIENT=edge react-app-rewired build && rm ./build/edge/manifest.firefox.json",
|
||||||
"build:firefox": "BUILD_PATH=./build/firefox REACT_APP_CLIENT=firefox react-app-rewired build && rm ./build/firefox/manifest.json && mv ./build/firefox/manifest.firefox.json ./build/firefox/manifest.json",
|
"build:firefox": "BUILD_PATH=./build/firefox REACT_APP_CLIENT=firefox react-app-rewired build && rm ./build/firefox/manifest.json && mv ./build/firefox/manifest.firefox.json ./build/firefox/manifest.json",
|
||||||
|
"build:userscript": "BUILD_PATH=./build/userscript REACT_APP_CLIENT=userscript react-app-rewired build",
|
||||||
"build:all": "yarn build && yarn build:edge && yarn build:firefox",
|
"build:all": "yarn build && yarn build:edge && yarn build:firefox",
|
||||||
"dist": "yarn build:all && rm -r dist && cp -r build dist",
|
"dist": "yarn build:all && rm -r dist && cp -r build dist",
|
||||||
"test": "react-app-rewired test",
|
"test": "react-app-rewired test",
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
const URL_APP_HOMEPAGE = "https://github.com/fishjar/kiss-translator";
|
|
||||||
|
|
||||||
export const UI_LANGS = [
|
export const UI_LANGS = [
|
||||||
["zh", "中文"],
|
["zh", "中文"],
|
||||||
["en", "English"],
|
["en", "English"],
|
||||||
@@ -35,8 +33,8 @@ export const I18N = {
|
|||||||
en: `README.en.md`,
|
en: `README.en.md`,
|
||||||
},
|
},
|
||||||
about_md_local: {
|
about_md_local: {
|
||||||
zh: `请 [点击这里](${URL_APP_HOMEPAGE}) 查看详情。`,
|
zh: `请 [点击这里](${process.env.REACT_APP_HOMEPAGE}) 查看详情。`,
|
||||||
en: `Please [click here](${URL_APP_HOMEPAGE}) for details.`,
|
en: `Please [click here](${process.env.REACT_APP_HOMEPAGE}) for details.`,
|
||||||
},
|
},
|
||||||
ui_lang: {
|
ui_lang: {
|
||||||
zh: `界面语言`,
|
zh: `界面语言`,
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ export const DEFAULT_RULE = {
|
|||||||
fromLang: "auto",
|
fromLang: "auto",
|
||||||
toLang: "zh-CN",
|
toLang: "zh-CN",
|
||||||
textStyle: OPT_STYLE_DASHLINE,
|
textStyle: OPT_STYLE_DASHLINE,
|
||||||
transOpen: true,
|
transOpen: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DEFAULT_SETTING = {
|
export const DEFAULT_SETTING = {
|
||||||
|
|||||||
120
src/content.js
120
src/content.js
@@ -1,129 +1,13 @@
|
|||||||
import { browser } from "./libs/browser";
|
import { browser } from "./libs/browser";
|
||||||
import { createRoot } from "react-dom/client";
|
|
||||||
import {
|
import {
|
||||||
APP_LCNAME,
|
|
||||||
MSG_TRANS_TOGGLE,
|
MSG_TRANS_TOGGLE,
|
||||||
MSG_TRANS_GETRULE,
|
MSG_TRANS_GETRULE,
|
||||||
MSG_TRANS_PUTRULE,
|
MSG_TRANS_PUTRULE,
|
||||||
TRANS_MIN_LENGTH,
|
|
||||||
TRANS_MAX_LENGTH,
|
|
||||||
} from "./config";
|
} from "./config";
|
||||||
import Content from "./views/Content";
|
import { getRules, matchRule } from "./libs";
|
||||||
import { StoragesProvider } from "./hooks/Storage";
|
|
||||||
import { queryEls, getRules, matchRule } from "./libs";
|
|
||||||
import { getSetting } from "./libs";
|
import { getSetting } from "./libs";
|
||||||
import { transPool } from "./libs/pool";
|
import { transPool } from "./libs/pool";
|
||||||
|
import { Translator } from "./libs/translator";
|
||||||
/**
|
|
||||||
* 翻译类
|
|
||||||
*/
|
|
||||||
class Translator {
|
|
||||||
_rule = {};
|
|
||||||
|
|
||||||
_interseObserver = new IntersectionObserver(
|
|
||||||
(intersections) => {
|
|
||||||
intersections.forEach((intersection) => {
|
|
||||||
if (intersection.isIntersecting) {
|
|
||||||
this._render(intersection.target);
|
|
||||||
this._interseObserver.unobserve(intersection.target);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
{
|
|
||||||
threshold: 0.1,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
_mutaObserver = new MutationObserver((mutations) => {
|
|
||||||
mutations.forEach((mutation) => {
|
|
||||||
mutation.addedNodes.forEach((node) => {
|
|
||||||
try {
|
|
||||||
queryEls(this._rule.selector, node).forEach((el) => {
|
|
||||||
this._interseObserver.observe(el);
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
constructor(rule) {
|
|
||||||
this._rule = rule;
|
|
||||||
if (rule.transOpen) {
|
|
||||||
this._register();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get rule() {
|
|
||||||
return this._rule;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateRule = (obj) => {
|
|
||||||
this._rule = { ...this._rule, ...obj };
|
|
||||||
};
|
|
||||||
|
|
||||||
toggle = () => {
|
|
||||||
if (this._rule.transOpen) {
|
|
||||||
this._rule.transOpen = false;
|
|
||||||
this._unRegister();
|
|
||||||
} else {
|
|
||||||
this._rule.transOpen = true;
|
|
||||||
this._register();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
_register = () => {
|
|
||||||
// 监听节点变化
|
|
||||||
this._mutaObserver.observe(document, {
|
|
||||||
childList: true,
|
|
||||||
subtree: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 监听节点显示
|
|
||||||
queryEls(this._rule.selector).forEach((el) => {
|
|
||||||
this._interseObserver.observe(el);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
_unRegister = () => {
|
|
||||||
// 解除节点变化监听
|
|
||||||
this._mutaObserver.disconnect();
|
|
||||||
|
|
||||||
// 解除节点显示监听
|
|
||||||
queryEls(this._rule.selector).forEach((el) =>
|
|
||||||
this._interseObserver.unobserve(el)
|
|
||||||
);
|
|
||||||
|
|
||||||
// 移除已插入元素
|
|
||||||
queryEls(APP_LCNAME).forEach((el) => el.remove());
|
|
||||||
};
|
|
||||||
|
|
||||||
_render = (el) => {
|
|
||||||
if (el.querySelector(APP_LCNAME)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 除openai外,保留code和a标签
|
|
||||||
const q = el.innerText.trim();
|
|
||||||
if (!q || q.length < TRANS_MIN_LENGTH || q.length > TRANS_MAX_LENGTH) {
|
|
||||||
// 太长或太短不翻译
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// console.log("---> ", q);
|
|
||||||
|
|
||||||
const span = document.createElement(APP_LCNAME);
|
|
||||||
el.appendChild(span);
|
|
||||||
|
|
||||||
const root = createRoot(span);
|
|
||||||
root.render(
|
|
||||||
<StoragesProvider>
|
|
||||||
<Content q={q} rule={this._rule} />
|
|
||||||
</StoragesProvider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 入口函数
|
* 入口函数
|
||||||
|
|||||||
@@ -2,7 +2,14 @@ import storage from "./storage";
|
|||||||
import { STOKEY_MSAUTH, URL_MICROSOFT_AUTH } from "../config";
|
import { STOKEY_MSAUTH, URL_MICROSOFT_AUTH } from "../config";
|
||||||
import { fetchPolyfill } from "./fetch";
|
import { fetchPolyfill } from "./fetch";
|
||||||
|
|
||||||
const parseMSToken = (token) => JSON.parse(atob(token.split(".")[1])).exp;
|
const parseMSToken = (token) => {
|
||||||
|
try {
|
||||||
|
return JSON.parse(atob(token.split(".")[1])).exp;
|
||||||
|
} catch (err) {
|
||||||
|
console.log("[parseMSToken]", err);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 闭包缓存token,减少对storage查询
|
* 闭包缓存token,减少对storage查询
|
||||||
|
|||||||
@@ -13,20 +13,29 @@ import {
|
|||||||
* @param {*} init
|
* @param {*} init
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const fetchGM = async (input, { method, headers, body }) =>
|
const fetchGM = async (input, { method = "GET", headers, body } = {}) =>
|
||||||
new Promise((resolve, reject) => {
|
new Promise((resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
window.GM.xmlhttpRequest({
|
window.GM_xmlhttpRequest({
|
||||||
method,
|
method,
|
||||||
url: input,
|
url: input,
|
||||||
headers,
|
headers,
|
||||||
data: body,
|
data: body,
|
||||||
onload: (response) => {
|
onload: (response) => {
|
||||||
resolve(new Response(response.response));
|
if (response.status === 200) {
|
||||||
},
|
const headers = new Headers();
|
||||||
onerror: (error) => {
|
response.responseHeaders.split("\n").forEach((line) => {
|
||||||
reject(error);
|
let [name, value] = line.split(":").map((item) => item.trim());
|
||||||
|
if (name && value) {
|
||||||
|
headers.append(name, value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
resolve(new Response(response.response, { headers }));
|
||||||
|
} else {
|
||||||
|
reject(new Error(`[${response.status}] ${response.responseText}`));
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
onerror: reject,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
reject(error);
|
reject(error);
|
||||||
|
|||||||
@@ -21,11 +21,11 @@ async function set(key, val) {
|
|||||||
|
|
||||||
async function get(key) {
|
async function get(key) {
|
||||||
if (isExt) {
|
if (isExt) {
|
||||||
const res = await browser.storage.local.get([key]);
|
const val = await browser.storage.local.get([key]);
|
||||||
return res[key];
|
return val[key];
|
||||||
} else if (isGm) {
|
} else if (isGm) {
|
||||||
const res = await window.GM.getValue(key);
|
const val = await window.GM.getValue(key);
|
||||||
return res;
|
return val;
|
||||||
}
|
}
|
||||||
return window.localStorage.getItem(key);
|
return window.localStorage.getItem(key);
|
||||||
}
|
}
|
||||||
|
|||||||
116
src/libs/translator.js
Normal file
116
src/libs/translator.js
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
import { createRoot } from "react-dom/client";
|
||||||
|
import { APP_LCNAME, TRANS_MIN_LENGTH, TRANS_MAX_LENGTH } from "../config";
|
||||||
|
import { StoragesProvider } from "../hooks/Storage";
|
||||||
|
import { queryEls } from ".";
|
||||||
|
import Content from "../views/Content";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 翻译类
|
||||||
|
*/
|
||||||
|
export class Translator {
|
||||||
|
_rule = {};
|
||||||
|
|
||||||
|
_interseObserver = new IntersectionObserver(
|
||||||
|
(intersections) => {
|
||||||
|
intersections.forEach((intersection) => {
|
||||||
|
if (intersection.isIntersecting) {
|
||||||
|
this._render(intersection.target);
|
||||||
|
this._interseObserver.unobserve(intersection.target);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{
|
||||||
|
threshold: 0.1,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
_mutaObserver = new MutationObserver((mutations) => {
|
||||||
|
mutations.forEach((mutation) => {
|
||||||
|
mutation.addedNodes.forEach((node) => {
|
||||||
|
try {
|
||||||
|
queryEls(this._rule.selector, node).forEach((el) => {
|
||||||
|
this._interseObserver.observe(el);
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
constructor(rule) {
|
||||||
|
this._rule = rule;
|
||||||
|
if (rule.transOpen) {
|
||||||
|
this._register();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get rule() {
|
||||||
|
return this._rule;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateRule = (obj) => {
|
||||||
|
this._rule = { ...this._rule, ...obj };
|
||||||
|
};
|
||||||
|
|
||||||
|
toggle = () => {
|
||||||
|
if (this._rule.transOpen) {
|
||||||
|
this._rule.transOpen = false;
|
||||||
|
this._unRegister();
|
||||||
|
} else {
|
||||||
|
this._rule.transOpen = true;
|
||||||
|
this._register();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_register = () => {
|
||||||
|
// 监听节点变化
|
||||||
|
this._mutaObserver.observe(document, {
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听节点显示
|
||||||
|
queryEls(this._rule.selector).forEach((el) => {
|
||||||
|
this._interseObserver.observe(el);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
_unRegister = () => {
|
||||||
|
// 解除节点变化监听
|
||||||
|
this._mutaObserver.disconnect();
|
||||||
|
|
||||||
|
// 解除节点显示监听
|
||||||
|
queryEls(this._rule.selector).forEach((el) =>
|
||||||
|
this._interseObserver.unobserve(el)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 移除已插入元素
|
||||||
|
queryEls(APP_LCNAME).forEach((el) => el.remove());
|
||||||
|
};
|
||||||
|
|
||||||
|
_render = (el) => {
|
||||||
|
if (el.querySelector(APP_LCNAME)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 除openai外,保留code和a标签
|
||||||
|
const q = el.innerText.trim();
|
||||||
|
if (!q || q.length < TRANS_MIN_LENGTH || q.length > TRANS_MAX_LENGTH) {
|
||||||
|
// 太长或太短不翻译
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.log("---> ", q);
|
||||||
|
|
||||||
|
const span = document.createElement(APP_LCNAME);
|
||||||
|
el.appendChild(span);
|
||||||
|
|
||||||
|
const root = createRoot(span);
|
||||||
|
root.render(
|
||||||
|
<StoragesProvider>
|
||||||
|
<Content q={q} rule={this._rule} />
|
||||||
|
</StoragesProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
39
src/userscript.js
Normal file
39
src/userscript.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { browser } from "./libs/browser";
|
||||||
|
import {
|
||||||
|
MSG_TRANS_TOGGLE,
|
||||||
|
MSG_TRANS_GETRULE,
|
||||||
|
MSG_TRANS_PUTRULE,
|
||||||
|
} from "./config";
|
||||||
|
import { getRules, matchRule } from "./libs";
|
||||||
|
import { getSetting } from "./libs";
|
||||||
|
import { transPool } from "./libs/pool";
|
||||||
|
import { Translator } from "./libs/translator";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 入口函数
|
||||||
|
*/
|
||||||
|
(async () => {
|
||||||
|
const { fetchInterval, fetchLimit } = await getSetting();
|
||||||
|
transPool.update(fetchInterval, fetchLimit);
|
||||||
|
|
||||||
|
const rules = await getRules();
|
||||||
|
const rule = matchRule(rules, document.location.href);
|
||||||
|
const translator = new Translator(rule);
|
||||||
|
|
||||||
|
// 监听消息
|
||||||
|
browser?.runtime.onMessage.addListener(async ({ action, args }) => {
|
||||||
|
switch (action) {
|
||||||
|
case MSG_TRANS_TOGGLE:
|
||||||
|
translator.toggle();
|
||||||
|
break;
|
||||||
|
case MSG_TRANS_GETRULE:
|
||||||
|
break;
|
||||||
|
case MSG_TRANS_PUTRULE:
|
||||||
|
translator.updateRule(args);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return { error: `message action is unavailable: ${action}` };
|
||||||
|
}
|
||||||
|
return { data: translator.rule };
|
||||||
|
});
|
||||||
|
})();
|
||||||
Reference in New Issue
Block a user