Compare commits

..

16 Commits

Author SHA1 Message Date
Gabe Yuan
1191791447 v1.5.4 2023-08-22 17:52:12 +08:00
Gabe Yuan
5c510f2df2 add rules filter when add rule 2023-08-22 17:51:40 +08:00
Gabe Yuan
7c0aa23177 add rules filter when add rule 2023-08-22 17:46:57 +08:00
Gabe Yuan
4bc1c26653 add rules filter when add rule 2023-08-22 17:37:42 +08:00
Gabe Yuan
ca1e1148d6 sync subscribe rules when browser start or userscript run 2023-08-22 16:27:09 +08:00
Gabe Yuan
2224455a7f add text description for rules 2023-08-22 10:35:57 +08:00
Gabe Yuan
f463f3ce08 v1.5.3 2023-08-21 23:50:32 +08:00
Gabe Yuan
c0872db98c auto use unsafe fetch 2023-08-21 23:50:14 +08:00
Gabe Yuan
d3a5d91f01 auto use unsafe fetch 2023-08-21 23:46:42 +08:00
Gabe Yuan
3e9338be0e v1.5.2 2023-08-21 22:24:42 +08:00
Gabe Yuan
ef7f1ad638 fetch subrules use unsafe fetch 2023-08-21 21:35:53 +08:00
Gabe Yuan
1f10ebe404 fetch subrules use unsafe fetch 2023-08-21 21:31:20 +08:00
Gabe Yuan
f4a8251c61 add shortcut: Toggle Style 2023-08-21 16:06:21 +08:00
Gabe Yuan
f585a43480 v1.5.1 2023-08-21 14:52:57 +08:00
Gabe Yuan
3a11465c24 fix stack useFlexGap 2023-08-21 14:43:22 +08:00
Gabe Yuan
3c3ebdf96c add command shortcuts & menu command 2023-08-21 14:03:39 +08:00
20 changed files with 407 additions and 147 deletions

3
.env
View File

@@ -2,11 +2,12 @@ GENERATE_SOURCEMAP=false
REACT_APP_NAME=KISS Translator
REACT_APP_NAME_CN=简约翻译
REACT_APP_VERSION=1.5.0
REACT_APP_VERSION=1.5.4
REACT_APP_HOMEPAGE=https://github.com/fishjar/kiss-translator
REACT_APP_OPTIONSPAGE=https://kiss-translator.rayjar.com/options
REACT_APP_OPTIONSPAGE2=https://fishjar.github.io/kiss-translator/options.html
REACT_APP_OPTIONSPAGE_DEV=http://localhost:3000/options.html
REACT_APP_LOGOURL=https://kiss-translator.rayjar.com/images/logo192.png
REACT_APP_RULESURL=https://kiss-translator.rayjar.com/kiss-translator-rules.json
REACT_APP_USERSCRIPT_DOWNLOADURL=https://kiss-translator.rayjar.com/kiss-translator.user.js
REACT_APP_USERSCRIPT_DOWNLOADURL2=https://fishjar.github.io/kiss-translator/kiss-translator.user.js

View File

@@ -84,9 +84,11 @@ const userscriptWebpack = (config, env) => {
// @downloadURL ${process.env.REACT_APP_USERSCRIPT_DOWNLOADURL}
// @updateURL ${process.env.REACT_APP_USERSCRIPT_DOWNLOADURL}
// @grant GM.xmlHttpRequest
// @grant GM.registerMenuCommand
// @grant GM.setValue
// @grant GM.getValue
// @grant GM.deleteValue
// @grant GM.info
// @grant unsafeWindow
// @connect translate.googleapis.com
// @connect api-edge.cognitive.microsofttranslator.com
@@ -97,6 +99,7 @@ const userscriptWebpack = (config, env) => {
// @connect github.io
// @connect githubusercontent.com
// @connect kiss-translator.rayjar.com
// @connect ghproxy.com
// @run-at document-end
// ==/UserScript==

View File

@@ -1,7 +1,7 @@
{
"name": "kiss-translator",
"description": "A minimalist bilingual translation Extension & Greasemonkey Script",
"version": "1.5.0",
"version": "1.5.4",
"author": "Gabe<yugang2002@gmail.com>",
"private": true,
"dependencies": {

View File

@@ -4,5 +4,11 @@
},
"app_description": {
"message": "A minimalist bilingual translation Extension & Greasemonkey Script"
},
"toggle_translate": {
"message": "Toggle Translate"
},
"toggle_style": {
"message": "Toggle Style"
}
}

View File

@@ -4,5 +4,11 @@
},
"app_description": {
"message": "一个简约的双语网页翻译扩展 & 油猴脚本"
},
"toggle_translate": {
"message": "切换翻译"
},
"toggle_style": {
"message": "切换样式"
}
}

View File

@@ -2,7 +2,7 @@
"manifest_version": 2,
"name": "__MSG_app_name__",
"description": "__MSG_app_description__",
"version": "1.5.0",
"version": "1.5.4",
"default_locale": "en",
"author": "Gabe<yugang2002@gmail.com>",
"homepage_url": "https://github.com/fishjar/kiss-translator",
@@ -15,6 +15,20 @@
"matches": ["<all_urls>"]
}
],
"commands": {
"toggleTranslate": {
"suggested_key": {
"default": "Alt+Q"
},
"description": "__MSG_toggle_translate__"
},
"toggleStyle": {
"suggested_key": {
"default": "Alt+C"
},
"description": "__MSG_toggle_style__"
}
},
"permissions": ["<all_urls>", "storage"],
"icons": {
"16": "images/logo16.png",

View File

@@ -2,7 +2,7 @@
"manifest_version": 3,
"name": "__MSG_app_name__",
"description": "__MSG_app_description__",
"version": "1.5.0",
"version": "1.5.4",
"default_locale": "en",
"author": "Gabe<yugang2002@gmail.com>",
"homepage_url": "https://github.com/fishjar/kiss-translator",
@@ -16,6 +16,20 @@
"matches": ["<all_urls>"]
}
],
"commands": {
"toggleTranslate": {
"suggested_key": {
"default": "Alt+Q"
},
"description": "__MSG_toggle_translate__"
},
"toggleStyle": {
"suggested_key": {
"default": "Alt+C"
},
"description": "__MSG_toggle_style__"
}
},
"permissions": ["storage"],
"host_permissions": ["<all_urls>"],
"icons": {

View File

@@ -20,19 +20,16 @@ import { sha256 } from "../libs/utils";
* @param {*} data
* @returns
*/
export const apiSyncData = async (url, key, data) =>
fetchPolyfill(
url,
{
headers: {
"Content-type": "application/json",
Authorization: `Bearer ${await sha256(key, KV_SALT_SYNC)}`,
},
method: "POST",
body: JSON.stringify(data),
export const apiSyncData = async (url, key, data, isBg = false) =>
fetchPolyfill(url, {
headers: {
"Content-type": "application/json",
Authorization: `Bearer ${await sha256(key, KV_SALT_SYNC)}`,
},
{ useUnsafe: true }
);
method: "POST",
body: JSON.stringify(data),
isBg,
});
/**
* 谷歌翻译
@@ -53,15 +50,14 @@ const apiGoogleTranslate = async (translator, text, to, from) => {
};
const { googleUrl } = await getSetting();
const input = `${googleUrl}?${queryString.stringify(params)}`;
return fetchPolyfill(
input,
{
headers: {
"Content-type": "application/json",
},
return fetchPolyfill(input, {
headers: {
"Content-type": "application/json",
},
{ useCache: true, usePool: true, translator }
);
useCache: true,
usePool: true,
translator,
});
};
/**
@@ -78,17 +74,16 @@ const apiMicrosoftTranslate = (translator, text, to, from) => {
"api-version": "3.0",
};
const input = `${URL_MICROSOFT_TRANS}?${queryString.stringify(params)}`;
return fetchPolyfill(
input,
{
headers: {
"Content-type": "application/json",
},
method: "POST",
body: JSON.stringify([{ Text: text }]),
return fetchPolyfill(input, {
headers: {
"Content-type": "application/json",
},
{ useCache: true, usePool: true, translator }
);
method: "POST",
body: JSON.stringify([{ Text: text }]),
useCache: true,
usePool: true,
translator,
});
};
/**
@@ -104,31 +99,31 @@ const apiOpenaiTranslate = async (translator, text, to, from) => {
let prompt = openaiPrompt
.replaceAll(PROMPT_PLACE_FROM, from)
.replaceAll(PROMPT_PLACE_TO, to);
return fetchPolyfill(
openaiUrl,
{
headers: {
"Content-type": "application/json",
},
method: "POST",
body: JSON.stringify({
model: openaiModel,
messages: [
{
role: "system",
content: prompt,
},
{
role: "user",
content: text,
},
],
temperature: 0,
max_tokens: 256,
}),
return fetchPolyfill(openaiUrl, {
headers: {
"Content-type": "application/json",
},
{ useCache: true, usePool: true, translator, token: openaiKey }
);
method: "POST",
body: JSON.stringify({
model: openaiModel,
messages: [
{
role: "system",
content: prompt,
},
{
role: "user",
content: text,
},
],
temperature: 0,
max_tokens: 256,
}),
useCache: true,
usePool: true,
translator,
token: openaiKey,
});
};
/**

View File

@@ -3,6 +3,10 @@ import {
MSG_FETCH,
MSG_FETCH_LIMIT,
MSG_FETCH_CLEAR,
MSG_TRANS_TOGGLE,
MSG_TRANS_TOGGLE_STYLE,
CMD_TOGGLE_TRANSLATE,
CMD_TOGGLE_STYLE,
DEFAULT_SETTING,
DEFAULT_RULES,
DEFAULT_SYNC,
@@ -10,11 +14,15 @@ import {
STOKEY_RULES,
STOKEY_SYNC,
CACHE_NAME,
STOKEY_RULESCACHE_PREFIX,
BUILTIN_RULES,
} from "./config";
import storage from "./libs/storage";
import { getSetting } from "./libs";
import { syncAll } from "./libs/sync";
import { fetchData, fetchPool } from "./libs/fetch";
import { sendTabMsg } from "./libs/msg";
import { trySyncAllSubRules } from "./libs/rules";
/**
* 插件安装
@@ -24,7 +32,10 @@ browser.runtime.onInstalled.addListener(() => {
storage.trySetObj(STOKEY_SETTING, DEFAULT_SETTING);
storage.trySetObj(STOKEY_RULES, DEFAULT_RULES);
storage.trySetObj(STOKEY_SYNC, DEFAULT_SYNC);
// todo缓存内置rules
storage.trySetObj(
`${STOKEY_RULESCACHE_PREFIX}${process.env.REACT_APP_RULESURL}`,
BUILTIN_RULES
);
});
/**
@@ -34,13 +45,16 @@ browser.runtime.onStartup.addListener(async () => {
console.log("browser onStartup");
// 同步数据
await syncAll();
await syncAll(true);
// 清除缓存
const { clearCache } = await getSetting();
if (clearCache) {
const setting = await getSetting();
if (setting.clearCache) {
caches.delete(CACHE_NAME);
}
// 同步订阅规则
trySyncAllSubRules(setting, true);
});
/**
@@ -50,8 +64,8 @@ browser.runtime.onMessage.addListener(
({ action, args }, sender, sendResponse) => {
switch (action) {
case MSG_FETCH:
const { input, init, opts } = args;
fetchData(input, init, opts)
const { input, opts } = args;
fetchData(input, opts)
.then((data) => {
sendResponse({ data });
})
@@ -74,3 +88,19 @@ browser.runtime.onMessage.addListener(
return true;
}
);
/**
* 监听快捷键
*/
browser.commands.onCommand.addListener((command) => {
// console.log(`Command: ${command}`);
switch (command) {
case CMD_TOGGLE_TRANSLATE:
sendTabMsg(MSG_TRANS_TOGGLE);
break;
case CMD_TOGGLE_STYLE:
sendTabMsg(MSG_TRANS_TOGGLE_STYLE);
break;
default:
}
});

View File

@@ -108,9 +108,9 @@ export const I18N = {
zh: `注入订阅规则`,
en: `Inject Subscribe Rules`,
},
edit_rules: {
zh: `编辑规则`,
en: `Edit Rules`,
personal_rules: {
zh: `个人规则`,
en: `Personal Rules`,
},
subscribe_rules: {
zh: `订阅规则`,
@@ -120,6 +120,14 @@ export const I18N = {
zh: `订阅地址`,
en: `Subscribe URL`,
},
rules_warn_1: {
zh: `1、“个人规则”一直生效选择“注入订阅规则”后“订阅规则”才会生效。`,
en: `1. The "Personal Rules" are always in effect. After selecting "Inject Subscription Rules", the "Subscription Rules" will take effect.`,
},
rules_warn_2: {
zh: `2、“订阅规则”的注入位置是倒数第二的位置因此除全局规则(*)外,“个人规则”优先级比“订阅规则”高,“个人规则”填写同样的网址会覆盖”订阅规则“的条目。`,
en: `2. The injection position of "Subscription Rules" is the penultimate position. Therefore, except for the global rules (*), the priority of "Personal Rules" is higher than that of "Subscription Rules". Filling in the same url in "Personal Rules" will overwrite "Subscription Rules" entry.`,
},
sync_warn: {
zh: `如果服务器存在其他客户端同步的数据,第一次同步将直接覆盖本地配置,后面则根据修改时间,新的覆盖旧的。`,
en: `If the server has data synchronized by other clients, the first synchronization will directly overwrite the local configuration, and later, according to the modification time, the new one will overwrite the old one.`,

View File

@@ -18,6 +18,9 @@ export const STOKEY_SYNC = `${APP_NAME}_sync`;
export const STOKEY_FAB = `${APP_NAME}_fab`;
export const STOKEY_RULESCACHE_PREFIX = `${APP_NAME}_rulescache_`;
export const CMD_TOGGLE_TRANSLATE = "toggleTranslate";
export const CMD_TOGGLE_STYLE = "toggleStyle";
export const CLIENT_WEB = "web";
export const CLIENT_CHROME = "chrome";
export const CLIENT_EDGE = "edge";
@@ -37,6 +40,7 @@ export const MSG_FETCH = "fetch";
export const MSG_FETCH_LIMIT = "fetch_limit";
export const MSG_FETCH_CLEAR = "fetch_clear";
export const MSG_TRANS_TOGGLE = "trans_toggle";
export const MSG_TRANS_TOGGLE_STYLE = "trans_toggle_style";
export const MSG_TRANS_GETRULE = "trans_getrule";
export const MSG_TRANS_PUTRULE = "trans_putrule";
export const MSG_TRANS_CURRULE = "trans_currule";
@@ -153,7 +157,7 @@ export const GLOBLA_RULE = {
// 订阅列表
export const DEFAULT_SUBRULES_LIST = [
{
url: "https://kiss-translator.rayjar.com/kiss-translator-rules.json",
url: process.env.REACT_APP_RULESURL,
selected: true,
},
{
@@ -188,4 +192,5 @@ export const DEFAULT_SYNC = {
settingSyncAt: 0,
rulesUpdateAt: 0,
rulesSyncAt: 0,
subRulesSyncAt: 0, // 订阅规则同步时间
};

View File

@@ -1,6 +1,7 @@
import { browser } from "./libs/browser";
import {
MSG_TRANS_TOGGLE,
MSG_TRANS_TOGGLE_STYLE,
MSG_TRANS_GETRULE,
MSG_TRANS_PUTRULE,
} from "./config";
@@ -22,6 +23,9 @@ import { Translator } from "./libs/translator";
case MSG_TRANS_TOGGLE:
translator.toggle();
break;
case MSG_TRANS_TOGGLE_STYLE:
translator.toggleStyle();
break;
case MSG_TRANS_GETRULE:
break;
case MSG_TRANS_PUTRULE:

View File

@@ -65,7 +65,7 @@ const newCacheReq = async (request) => {
* @param {*} param0
* @returns
*/
const fetchApi = async ({ input, init, useUnsafe, translator, token }) => {
const fetchApi = async ({ input, init = {}, translator, token }) => {
if (translator === OPT_TRANS_MICROSOFT) {
init.headers["Authorization"] = `Bearer ${token}`;
} else if (translator === OPT_TRANS_OPENAI) {
@@ -73,8 +73,13 @@ const fetchApi = async ({ input, init, useUnsafe, translator, token }) => {
init.headers["api-key"] = token; // Azure OpenAI
}
if (isGm && !useUnsafe) {
return fetchGM(input, init);
if (isGm) {
const connects = GM?.info?.script?.connects || [];
const url = new URL(input);
const isSafe = connects.find((item) => url.hostname.endsWith(item));
if (isSafe) {
return fetchGM(input, init);
}
}
return fetch(input, init);
};
@@ -98,14 +103,12 @@ export const fetchPool = taskPool(
/**
* 请求数据统一接口
* @param {*} input
* @param {*} init
* @param {*} opts
* @returns
*/
export const fetchData = async (
input,
init,
{ useCache, usePool, translator, useUnsafe, token } = {}
{ useCache, usePool, translator, token, ...init } = {}
) => {
const cacheReq = await newCacheReq(new Request(input, init));
const cache = await caches.open(CACHE_NAME);
@@ -123,9 +126,9 @@ export const fetchData = async (
if (!res) {
// 发送请求
if (usePool) {
res = await fetchPool.push({ input, init, useUnsafe, translator, token });
res = await fetchPool.push({ input, init, translator, token });
} else {
res = await fetchApi({ input, init, useUnsafe, translator, token });
res = await fetchApi({ input, init, translator, token });
}
if (!res?.ok) {
@@ -152,22 +155,21 @@ export const fetchData = async (
/**
* fetch 兼容性封装
* @param {*} input
* @param {*} init
* @param {*} opts
* @returns
*/
export const fetchPolyfill = async (input, init, opts) => {
export const fetchPolyfill = async (input, { isBg = false, ...opts } = {}) => {
// 插件
if (isExt) {
const res = await sendMsg(MSG_FETCH, { input, init, opts });
if (isExt && !isBg) {
const res = await sendMsg(MSG_FETCH, { input, opts });
if (res.error) {
throw new Error(res.error);
}
return res.data;
}
// 油猴/网页
return await fetchData(input, init, opts);
// 油猴/网页/BackgroundPage
return await fetchData(input, opts);
};
/**

View File

@@ -10,7 +10,7 @@ import {
} from "../config";
import { browser } from "./browser";
import { isMatch } from "./utils";
import { tryLoadRules } from "./rules";
import { loadSubRules } from "./rules";
/**
* 获取节点列表并转为数组
@@ -57,13 +57,14 @@ export const setFab = async (obj) => await storage.setObj(STOKEY_FAB, obj);
export const matchRule = async (
rules,
href,
{ injectRules, subrulesList = DEFAULT_SUBRULES_LIST }
{ injectRules = true, subrulesList = DEFAULT_SUBRULES_LIST }
) => {
rules = [...rules];
if (injectRules) {
try {
const selectedSub = subrulesList.find((item) => item.selected);
if (selectedSub?.url) {
const subRules = await tryLoadRules(selectedSub.url);
const subRules = await loadSubRules(selectedSub.url);
rules.splice(-1, 0, ...subRules);
}
} catch (err) {
@@ -71,13 +72,13 @@ export const matchRule = async (
}
}
const rule = rules.find((rule) =>
rule.pattern.split(",").some((p) => isMatch(href, p.trim()))
const rule = rules.find((r) =>
r.pattern.split(",").some((p) => isMatch(href, p.trim()))
);
const globalRule =
rules.find((rule) =>
rule.pattern.split(",").some((p) => p.trim() === "*")
) || GLOBLA_RULE;
rules.find((r) => r.pattern.split(",").some((p) => p.trim() === "*")) ||
GLOBLA_RULE;
if (!rule) {
return globalRule;

View File

@@ -9,6 +9,7 @@ import {
OPT_LANGS_FROM,
OPT_LANGS_TO,
} from "../config";
import { syncOpt } from "./sync";
const fromLangs = OPT_LANGS_FROM.map((item) => item[0]);
const toLangs = OPT_LANGS_TO.map((item) => item[0]);
@@ -62,11 +63,11 @@ export const checkRules = (rules) => {
};
/**
* 本地rules缓存
* 订阅规则的本地缓存
*/
export const rulesCache = {
fetch: async (url) => {
const res = await fetchPolyfill(url);
fetch: async (url, isBg = false) => {
const res = await fetchPolyfill(url, { isBg });
const rules = checkRules(res).filter(
(rule) => rule.pattern.replaceAll(GLOBAL_KEY, "") !== ""
);
@@ -84,16 +85,61 @@ export const rulesCache = {
};
/**
* 从缓存或远程加载订阅的rules
* 同步订阅规则
* @param {*} url
* @returns
*/
export const tryLoadRules = async (url) => {
let rules = await rulesCache.get(url);
export const syncSubRules = async (url, isBg = false) => {
const rules = await rulesCache.fetch(url, isBg);
if (rules.length > 0) {
await rulesCache.set(url, rules);
}
return rules;
};
/**
* 同步所有订阅规则
* @param {*} url
* @returns
*/
export const syncAllSubRules = async (subrulesList, isBg = false) => {
for (let subrules of subrulesList) {
try {
await syncSubRules(subrules.url, isBg);
} catch (err) {
console.log(`[sync subrule error]: ${subrules.url}`, err);
}
}
};
/**
* 根据时间同步所有订阅规则
* @param {*} url
* @returns
*/
export const trySyncAllSubRules = async ({ subrulesList }, isBg = false) => {
try {
const { subRulesSyncAt } = await syncOpt.load();
const now = Date.now();
const interval = 24 * 60 * 60 * 1000; // 间隔一天
if (now - subRulesSyncAt > interval) {
await syncAllSubRules(subrulesList, isBg);
await syncOpt.update({ subRulesSyncAt: now });
}
} catch (err) {
console.log("[try sync all subrules]", err);
}
};
/**
* 从缓存或远程加载订阅规则
* @param {*} url
* @returns
*/
export const loadSubRules = async (url) => {
const rules = await rulesCache.get(url);
if (rules?.length) {
return rules;
}
rules = await rulesCache.fetch(url);
await rulesCache.set(url, rules);
return rules;
return await syncSubRules(url);
};

View File

@@ -13,69 +13,95 @@ import { getSetting, getRules } from ".";
import { apiSyncData } from "../apis";
import { sha256 } from "./utils";
export const loadSyncOpt = async () =>
(await storage.getObj(STOKEY_SYNC)) || DEFAULT_SYNC;
/**
* 同步相关数据
*/
export const syncOpt = {
load: async () => (await storage.getObj(STOKEY_SYNC)) || DEFAULT_SYNC,
update: async (obj) => {
await storage.putObj(STOKEY_SYNC, obj);
},
};
export const syncSetting = async () => {
/**
* 同步设置
* @returns
*/
export const syncSetting = async (isBg = false) => {
try {
const { syncUrl, syncKey, settingUpdateAt } = await loadSyncOpt();
const { syncUrl, syncKey, settingUpdateAt } = await syncOpt.load();
if (!syncUrl || !syncKey) {
return;
}
const setting = await getSetting();
const res = await apiSyncData(syncUrl, syncKey, {
key: KV_SETTING_KEY,
value: setting,
updateAt: settingUpdateAt,
});
const res = await apiSyncData(
syncUrl,
syncKey,
{
key: KV_SETTING_KEY,
value: setting,
updateAt: settingUpdateAt,
},
isBg
);
if (res && res.updateAt > settingUpdateAt) {
await storage.putObj(STOKEY_SYNC, {
await syncOpt.update({
settingUpdateAt: res.updateAt,
settingSyncAt: res.updateAt,
});
await storage.setObj(STOKEY_SETTING, res.value);
} else {
await storage.putObj(STOKEY_SYNC, {
settingSyncAt: res.updateAt,
});
await syncOpt.update({ settingSyncAt: res.updateAt });
}
} catch (err) {
console.log("[sync setting]", err);
}
};
export const syncRules = async () => {
/**
* 同步规则
* @returns
*/
export const syncRules = async (isBg = false) => {
try {
const { syncUrl, syncKey, rulesUpdateAt } = await loadSyncOpt();
const { syncUrl, syncKey, rulesUpdateAt } = await syncOpt.load();
if (!syncUrl || !syncKey) {
return;
}
const rules = await getRules();
const res = await apiSyncData(syncUrl, syncKey, {
key: KV_RULES_KEY,
value: rules,
updateAt: rulesUpdateAt,
});
const res = await apiSyncData(
syncUrl,
syncKey,
{
key: KV_RULES_KEY,
value: rules,
updateAt: rulesUpdateAt,
},
isBg
);
if (res && res.updateAt > rulesUpdateAt) {
await storage.putObj(STOKEY_SYNC, {
await syncOpt.update({
rulesUpdateAt: res.updateAt,
rulesSyncAt: res.updateAt,
});
await storage.setObj(STOKEY_RULES, res.value);
} else {
await storage.putObj(STOKEY_SYNC, {
rulesSyncAt: res.updateAt,
});
await syncOpt.update({ rulesSyncAt: res.updateAt });
}
} catch (err) {
console.log("[sync rules]", err);
console.log("[sync user rules]", err);
}
};
/**
* 同步分享规则
* @param {*} param0
* @returns
*/
export const syncShareRules = async ({ rules, syncUrl, syncKey }) => {
await apiSyncData(syncUrl, syncKey, {
key: KV_RULES_SHARE_KEY,
@@ -87,7 +113,11 @@ export const syncShareRules = async ({ rules, syncUrl, syncKey }) => {
return shareUrl;
};
export const syncAll = async () => {
await syncSetting();
await syncRules();
/**
* 同步个人设置和规则
* @returns
*/
export const syncAll = async (isBg = false) => {
await syncSetting(isBg);
await syncRules(isBg);
};

View File

@@ -5,6 +5,8 @@ import {
TRANS_MAX_LENGTH,
EVENT_KISS,
MSG_TRANS_CURRULE,
OPT_STYLE_DASHLINE,
OPT_STYLE_FUZZY,
} from "../config";
import { queryEls } from ".";
import Content from "../views/Content";
@@ -86,6 +88,14 @@ export class Translator {
}
};
toggleStyle = () => {
const textStyle =
this.rule.textStyle === OPT_STYLE_FUZZY
? OPT_STYLE_DASHLINE
: OPT_STYLE_FUZZY;
this.rule = { ...this.rule, textStyle };
};
_register = () => {
// 监听节点变化
this._mutaObserver.observe(document, {

View File

@@ -5,6 +5,7 @@ import createCache from "@emotion/cache";
import { CacheProvider } from "@emotion/react";
import { getSetting, getRules, matchRule, getFab } from "./libs";
import { Translator } from "./libs/translator";
import { trySyncAllSubRules } from "./libs/rules";
/**
* 入口函数
@@ -54,4 +55,23 @@ import { Translator } from "./libs/translator";
</CacheProvider>
</React.StrictMode>
);
// 注册菜单
GM.registerMenuCommand(
"Toggle Translate",
(event) => {
translator.toggle();
},
"Q"
);
GM.registerMenuCommand(
"Toggle Style",
(event) => {
translator.toggleStyle();
},
"C"
);
// 同步订阅规则
trySyncAllSubRules(setting);
})();

View File

@@ -112,7 +112,9 @@ export default function Action({ translator, fab }) {
}
>
<Paper>
<Popup setShowPopup={setShowPopup} translator={translator} />
{showPopup && (
<Popup setShowPopup={setShowPopup} translator={translator} />
)}
</Paper>
</Draggable>
<Draggable

View File

@@ -3,6 +3,7 @@ import Stack from "@mui/material/Stack";
import TextField from "@mui/material/TextField";
import Button from "@mui/material/Button";
import CircularProgress from "@mui/material/CircularProgress";
import Alert from "@mui/material/Alert";
import {
GLOBAL_KEY,
DEFAULT_RULE,
@@ -11,7 +12,7 @@ import {
OPT_TRANS_ALL,
OPT_STYLE_ALL,
} from "../../config";
import { useState, useRef, useEffect } from "react";
import { useState, useRef, useEffect, useMemo } from "react";
import { useI18n } from "../../hooks/I18n";
import Typography from "@mui/material/Typography";
import Accordion from "@mui/material/Accordion";
@@ -35,11 +36,12 @@ import IconButton from "@mui/material/IconButton";
import ShareIcon from "@mui/icons-material/Share";
import SyncIcon from "@mui/icons-material/Sync";
import { useSubrules } from "../../hooks/Rules";
import { rulesCache, tryLoadRules } from "../../libs/rules";
import { rulesCache, loadSubRules, syncSubRules } from "../../libs/rules";
import { useAlert } from "../../hooks/Alert";
import { loadSyncOpt, syncShareRules } from "../../libs/sync";
import { syncOpt, syncShareRules } from "../../libs/sync";
import { debounce } from "../../libs/utils";
function RuleFields({ rule, rules, setShow }) {
function RuleFields({ rule, rules, setShow, setKeyword }) {
const initFormValues = rule || { ...DEFAULT_RULE, transOpen: "true" };
const editMode = !!rule;
@@ -73,10 +75,21 @@ function RuleFields({ rule, rules, setShow }) {
setErrors((pre) => ({ ...pre, [name]: "" }));
};
const handlePatternChange = useMemo(
() =>
debounce(async (patterns) => {
setKeyword(patterns.trim());
}, 500),
[setKeyword]
);
const handleChange = (e) => {
e.preventDefault();
const { name, value } = e.target;
setFormValues((pre) => ({ ...pre, [name]: value }));
if (name === "pattern" && !editMode) {
handlePatternChange(value);
}
};
const handleCancel = (e) => {
@@ -401,7 +414,7 @@ function ShareButton({ rules, injectRules, selectedSub }) {
const i18n = useI18n();
const handleClick = async () => {
try {
const { syncUrl, syncKey } = await loadSyncOpt();
const { syncUrl, syncKey } = await syncOpt.load();
if (!syncUrl || !syncKey) {
alert.warning(i18n("error_sync_setting"));
return;
@@ -409,7 +422,7 @@ function ShareButton({ rules, injectRules, selectedSub }) {
const shareRules = [...rules.list];
if (injectRules) {
const subRules = await tryLoadRules(selectedSub?.url);
const subRules = await loadSubRules(selectedSub?.url);
shareRules.splice(-1, 0, ...subRules);
}
@@ -445,6 +458,9 @@ function UserRules() {
const setting = useSetting();
const updateSetting = useSettingUpdate();
const subrules = useSubrules();
const [subRules, setSubRules] = useState([]);
const [keyword, setKeyword] = useState("");
const selectedSub = subrules.list.find((item) => item.selected);
const injectRules = !!setting?.injectRules;
@@ -477,9 +493,28 @@ function UserRules() {
});
};
useEffect(() => {
(async () => {
if (selectedSub?.url) {
try {
const rules = await loadSubRules(selectedSub?.url);
setSubRules(rules);
} catch (err) {
console.log("[load rules]", err);
}
}
})();
}, [selectedSub?.url]);
useEffect(() => {
if (!showAdd) {
setKeyword("");
}
}, [showAdd]);
return (
<Stack spacing={3}>
<Stack direction="row" spacing={2}>
<Stack direction="row" spacing={2} useFlexGap flexWrap="wrap">
<Button
size="small"
variant="contained"
@@ -516,13 +551,37 @@ function UserRules() {
/>
</Stack>
{showAdd && <RuleFields rules={rules} setShow={setShowAdd} />}
{showAdd && (
<RuleFields
rules={rules}
setShow={setShowAdd}
setKeyword={setKeyword}
/>
)}
<Box>
{rules.list.map((rule) => (
<RuleAccordion key={rule.pattern} rule={rule} rules={rules} />
))}
{rules.list
.filter(
(rule) =>
rule.pattern.includes(keyword) || keyword.includes(rule.pattern)
)
.map((rule) => (
<RuleAccordion key={rule.pattern} rule={rule} rules={rules} />
))}
</Box>
{injectRules && (
<Box>
{subRules
.filter(
(rule) =>
rule.pattern.includes(keyword) || keyword.includes(rule.pattern)
)
.map((rule) => (
<RuleAccordion key={rule.pattern} rule={rule} />
))}
</Box>
)}
</Stack>
);
}
@@ -542,13 +601,12 @@ function SubRulesItem({ index, url, selectedUrl, subrules, setRules }) {
const handleSync = async () => {
try {
setLoading(true);
const rules = await rulesCache.fetch(url);
await rulesCache.set(url, rules);
if (url === selectedUrl) {
const rules = await syncSubRules(url);
if (rules.length > 0 && url === selectedUrl) {
setRules(rules);
}
} catch (err) {
console.log("[sync rules]", err);
console.log("[sync sub rules]", err);
} finally {
setLoading(false);
}
@@ -603,11 +661,10 @@ function SubRulesEdit({ subrules }) {
}
try {
const rules = await rulesCache.fetch(url);
const rules = await syncSubRules(url);
if (rules.length === 0) {
throw new Error("empty rules");
}
await rulesCache.set(url, rules);
await subrules.add(url);
setShowInput(false);
setInputText("");
@@ -686,7 +743,7 @@ function SubRules() {
try {
setLoading(true);
const rules = await tryLoadRules(selectedSub?.url);
const rules = await loadSubRules(selectedSub?.url);
setRules(rules);
} catch (err) {
console.log("[load rules]", err);
@@ -738,9 +795,15 @@ export default function Rules() {
return (
<Box>
<Stack spacing={3}>
<Alert severity="info">
{i18n("rules_warn_1")}
<br />
{i18n("rules_warn_2")}
</Alert>
<Box sx={{ borderBottom: 1, borderColor: "divider" }}>
<Tabs value={activeTab} onChange={handleTabChange}>
<Tab label={i18n("edit_rules")} />
<Tab label={i18n("personal_rules")} />
<Tab label={i18n("subscribe_rules")} />
</Tabs>
</Box>