Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1191791447 | ||
|
|
5c510f2df2 | ||
|
|
7c0aa23177 | ||
|
|
4bc1c26653 | ||
|
|
ca1e1148d6 | ||
|
|
2224455a7f |
3
.env
3
.env
@@ -2,11 +2,12 @@ GENERATE_SOURCEMAP=false
|
||||
|
||||
REACT_APP_NAME=KISS Translator
|
||||
REACT_APP_NAME_CN=简约翻译
|
||||
REACT_APP_VERSION=1.5.3
|
||||
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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "kiss-translator",
|
||||
"description": "A minimalist bilingual translation Extension & Greasemonkey Script",
|
||||
"version": "1.5.3",
|
||||
"version": "1.5.4",
|
||||
"author": "Gabe<yugang2002@gmail.com>",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"manifest_version": 2,
|
||||
"name": "__MSG_app_name__",
|
||||
"description": "__MSG_app_description__",
|
||||
"version": "1.5.3",
|
||||
"version": "1.5.4",
|
||||
"default_locale": "en",
|
||||
"author": "Gabe<yugang2002@gmail.com>",
|
||||
"homepage_url": "https://github.com/fishjar/kiss-translator",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"manifest_version": 3,
|
||||
"name": "__MSG_app_name__",
|
||||
"description": "__MSG_app_description__",
|
||||
"version": "1.5.3",
|
||||
"version": "1.5.4",
|
||||
"default_locale": "en",
|
||||
"author": "Gabe<yugang2002@gmail.com>",
|
||||
"homepage_url": "https://github.com/fishjar/kiss-translator",
|
||||
|
||||
@@ -20,7 +20,7 @@ import { sha256 } from "../libs/utils";
|
||||
* @param {*} data
|
||||
* @returns
|
||||
*/
|
||||
export const apiSyncData = async (url, key, data) =>
|
||||
export const apiSyncData = async (url, key, data, isBg = false) =>
|
||||
fetchPolyfill(url, {
|
||||
headers: {
|
||||
"Content-type": "application/json",
|
||||
@@ -28,6 +28,7 @@ export const apiSyncData = async (url, key, data) =>
|
||||
},
|
||||
method: "POST",
|
||||
body: JSON.stringify(data),
|
||||
isBg,
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -49,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,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -74,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,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -100,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,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -14,12 +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";
|
||||
|
||||
/**
|
||||
* 插件安装
|
||||
@@ -29,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
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -39,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);
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -55,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 });
|
||||
})
|
||||
|
||||
@@ -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.`,
|
||||
|
||||
@@ -157,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,
|
||||
},
|
||||
{
|
||||
@@ -192,4 +192,5 @@ export const DEFAULT_SYNC = {
|
||||
settingSyncAt: 0,
|
||||
rulesUpdateAt: 0,
|
||||
rulesSyncAt: 0,
|
||||
subRulesSyncAt: 0, // 订阅规则同步时间
|
||||
};
|
||||
|
||||
@@ -65,7 +65,7 @@ const newCacheReq = async (request) => {
|
||||
* @param {*} param0
|
||||
* @returns
|
||||
*/
|
||||
const fetchApi = async ({ input, init, 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) {
|
||||
@@ -103,14 +103,12 @@ export const fetchPool = taskPool(
|
||||
/**
|
||||
* 请求数据统一接口
|
||||
* @param {*} input
|
||||
* @param {*} init
|
||||
* @param {*} opts
|
||||
* @returns
|
||||
*/
|
||||
export const fetchData = async (
|
||||
input,
|
||||
init,
|
||||
{ useCache, usePool, translator, token } = {}
|
||||
{ useCache, usePool, translator, token, ...init } = {}
|
||||
) => {
|
||||
const cacheReq = await newCacheReq(new Request(input, init));
|
||||
const cache = await caches.open(CACHE_NAME);
|
||||
@@ -157,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);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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 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);
|
||||
};
|
||||
|
||||
@@ -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";
|
||||
|
||||
/**
|
||||
* 入口函数
|
||||
@@ -70,4 +71,7 @@ import { Translator } from "./libs/translator";
|
||||
},
|
||||
"C"
|
||||
);
|
||||
|
||||
// 同步订阅规则
|
||||
trySyncAllSubRules(setting);
|
||||
})();
|
||||
|
||||
@@ -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,6 +493,25 @@ 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} useFlexGap flexWrap="wrap">
|
||||
@@ -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,9 +601,8 @@ 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) {
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user