This commit is contained in:
Gabe Yuan
2023-08-30 18:05:37 +08:00
parent d7cee8cca6
commit c46fe7d1c6
33 changed files with 770 additions and 559 deletions

View File

@@ -1,6 +1,7 @@
import queryString from "query-string"; import queryString from "query-string";
import { fetchPolyfill } from "../libs/fetch"; import { fetchPolyfill } from "../libs/fetch";
import { import {
GLOBAL_KEY,
OPT_TRANS_GOOGLE, OPT_TRANS_GOOGLE,
OPT_TRANS_MICROSOFT, OPT_TRANS_MICROSOFT,
OPT_TRANS_OPENAI, OPT_TRANS_OPENAI,
@@ -10,8 +11,9 @@ import {
PROMPT_PLACE_TO, PROMPT_PLACE_TO,
KV_SALT_SYNC, KV_SALT_SYNC,
} from "../config"; } from "../config";
import { getSetting, detectLang } from "../libs"; import { detectLang } from "../libs/browser";
import { sha256 } from "../libs/utils"; import { sha256 } from "../libs/utils";
import { checkRules } from "../libs/rules";
/** /**
* 同步数据 * 同步数据
@@ -31,6 +33,20 @@ export const apiSyncData = async (url, key, data, isBg = false) =>
isBg, isBg,
}); });
/**
* 下载订阅规则
* @param {*} url
* @param {*} isBg
* @returns
*/
export const apiFetchRules = async (url, isBg = false) => {
const res = await fetchPolyfill(url, { isBg });
const rules = checkRules(res).filter(
(rule) => rule.pattern.replaceAll(GLOBAL_KEY, "") !== ""
);
return rules;
};
/** /**
* 谷歌翻译 * 谷歌翻译
* @param {*} text * @param {*} text
@@ -38,7 +54,8 @@ export const apiSyncData = async (url, key, data, isBg = false) =>
* @param {*} from * @param {*} from
* @returns * @returns
*/ */
const apiGoogleTranslate = async (translator, text, to, from) => { const apiGoogleTranslate = async (translator, text, to, from, setting) => {
const { googleUrl } = setting;
const params = { const params = {
client: "gtx", client: "gtx",
dt: "t", dt: "t",
@@ -48,7 +65,6 @@ const apiGoogleTranslate = async (translator, text, to, from) => {
tl: to, tl: to,
q: text, q: text,
}; };
const { googleUrl } = await getSetting();
const input = `${googleUrl}?${queryString.stringify(params)}`; const input = `${googleUrl}?${queryString.stringify(params)}`;
return fetchPolyfill(input, { return fetchPolyfill(input, {
headers: { headers: {
@@ -93,9 +109,8 @@ const apiMicrosoftTranslate = (translator, text, to, from) => {
* @param {*} from * @param {*} from
* @returns * @returns
*/ */
const apiOpenaiTranslate = async (translator, text, to, from) => { const apiOpenaiTranslate = async (translator, text, to, from, setting) => {
const { openaiUrl, openaiKey, openaiModel, openaiPrompt } = const { openaiUrl, openaiKey, openaiModel, openaiPrompt } = setting;
await getSetting();
let prompt = openaiPrompt let prompt = openaiPrompt
.replaceAll(PROMPT_PLACE_FROM, from) .replaceAll(PROMPT_PLACE_FROM, from)
.replaceAll(PROMPT_PLACE_TO, to); .replaceAll(PROMPT_PLACE_TO, to);
@@ -131,7 +146,13 @@ const apiOpenaiTranslate = async (translator, text, to, from) => {
* @param {*} param0 * @param {*} param0
* @returns * @returns
*/ */
export const apiTranslate = async ({ translator, q, fromLang, toLang }) => { export const apiTranslate = async ({
translator,
q,
fromLang,
toLang,
setting,
}) => {
let trText = ""; let trText = "";
let isSame = false; let isSame = false;
@@ -139,7 +160,7 @@ export const apiTranslate = async ({ translator, q, fromLang, toLang }) => {
let to = OPT_LANGS_SPECIAL?.[translator]?.get(toLang) ?? toLang; let to = OPT_LANGS_SPECIAL?.[translator]?.get(toLang) ?? toLang;
if (translator === OPT_TRANS_GOOGLE) { if (translator === OPT_TRANS_GOOGLE) {
const res = await apiGoogleTranslate(translator, q, to, from); const res = await apiGoogleTranslate(translator, q, to, from, setting);
trText = res.sentences.map((item) => item.trans).join(" "); trText = res.sentences.map((item) => item.trans).join(" ");
isSame = to === res.src; isSame = to === res.src;
} else if (translator === OPT_TRANS_MICROSOFT) { } else if (translator === OPT_TRANS_MICROSOFT) {
@@ -147,9 +168,11 @@ export const apiTranslate = async ({ translator, q, fromLang, toLang }) => {
trText = res[0].translations[0].text; trText = res[0].translations[0].text;
isSame = to === res[0].detectedLanguage.language; isSame = to === res[0].detectedLanguage.language;
} else if (translator === OPT_TRANS_OPENAI) { } else if (translator === OPT_TRANS_OPENAI) {
const res = await apiOpenaiTranslate(translator, q, to, from); const res = await apiOpenaiTranslate(translator, q, to, from, setting);
trText = res?.choices?.[0].message.content; trText = res?.choices?.[0].message.content;
isSame = (await detectLang(q)) === (await detectLang(trText)); const sLang = await detectLang(q);
const tLang = await detectLang(trText);
isSame = q === trText || (sLang && tLang && sLang === tLang);
} }
return [trText, isSame]; return [trText, isSame];

View File

@@ -7,35 +7,19 @@ import {
MSG_TRANS_TOGGLE_STYLE, MSG_TRANS_TOGGLE_STYLE,
CMD_TOGGLE_TRANSLATE, CMD_TOGGLE_TRANSLATE,
CMD_TOGGLE_STYLE, CMD_TOGGLE_STYLE,
DEFAULT_SETTING,
DEFAULT_RULES,
DEFAULT_SYNC,
STOKEY_SETTING,
STOKEY_RULES,
STOKEY_SYNC,
CACHE_NAME,
STOKEY_RULESCACHE_PREFIX,
BUILTIN_RULES,
} from "./config"; } from "./config";
import storage from "./libs/storage"; import { getSettingWithDefault, tryInitDefaultData } from "./libs/storage";
import { getSetting } from "./libs";
import { trySyncAll } from "./libs/sync"; import { trySyncAll } from "./libs/sync";
import { fetchData, fetchPool } from "./libs/fetch"; import { fetchData, fetchPool } from "./libs/fetch";
import { sendTabMsg } from "./libs/msg"; import { sendTabMsg } from "./libs/msg";
import { trySyncAllSubRules } from "./libs/rules"; import { trySyncAllSubRules } from "./libs/subRules";
import { tryClearCaches } from "./libs";
/** /**
* 插件安装 * 插件安装
*/ */
browser.runtime.onInstalled.addListener(() => { browser.runtime.onInstalled.addListener(() => {
console.log("KISS Translator onInstalled"); tryInitDefaultData();
storage.trySetObj(STOKEY_SETTING, DEFAULT_SETTING);
storage.trySetObj(STOKEY_RULES, DEFAULT_RULES);
storage.trySetObj(STOKEY_SYNC, DEFAULT_SYNC);
storage.trySetObj(
`${STOKEY_RULESCACHE_PREFIX}${process.env.REACT_APP_RULESURL}`,
BUILTIN_RULES
);
}); });
/** /**
@@ -48,13 +32,9 @@ browser.runtime.onStartup.addListener(async () => {
await trySyncAll(true); await trySyncAll(true);
// 清除缓存 // 清除缓存
const setting = await getSetting(); const setting = await getSettingWithDefault();
if (setting.clearCache) { if (setting.clearCache) {
try { tryClearCaches();
caches.delete(CACHE_NAME);
} catch (err) {
console.log("[clean caches]", err.message);
}
} }
// 同步订阅规则 // 同步订阅规则

4
src/config/app.js Normal file
View File

@@ -0,0 +1,4 @@
export const APP_NAME = process.env.REACT_APP_NAME.trim()
.split(/\s+/)
.join("-");
export const APP_LCNAME = APP_NAME.toLowerCase();

View File

@@ -5,12 +5,9 @@ import {
DEFAULT_RULE, DEFAULT_RULE,
BUILTIN_RULES, BUILTIN_RULES,
} from "./rules"; } from "./rules";
import { APP_NAME, APP_LCNAME } from "./app";
export { I18N, UI_LANGS } from "./i18n"; export { I18N, UI_LANGS } from "./i18n";
export { GLOBAL_KEY, SHADOW_KEY, DEFAULT_RULE, BUILTIN_RULES }; export { GLOBAL_KEY, SHADOW_KEY, DEFAULT_RULE, BUILTIN_RULES, APP_LCNAME };
const APP_NAME = process.env.REACT_APP_NAME.trim().split(/\s+/).join("-");
export const APP_LCNAME = APP_NAME.toLowerCase();
export const STOKEY_MSAUTH = `${APP_NAME}_msauth`; export const STOKEY_MSAUTH = `${APP_NAME}_msauth`;
export const STOKEY_SETTING = `${APP_NAME}_setting`; export const STOKEY_SETTING = `${APP_NAME}_setting`;
@@ -163,6 +160,7 @@ export const DEFAULT_SUBRULES_LIST = [
}, },
{ {
url: "https://fishjar.github.io/kiss-translator/kiss-translator-rules.json", url: "https://fishjar.github.io/kiss-translator/kiss-translator-rules.json",
selected: false,
}, },
]; ];

View File

@@ -5,17 +5,18 @@ import {
MSG_TRANS_GETRULE, MSG_TRANS_GETRULE,
MSG_TRANS_PUTRULE, MSG_TRANS_PUTRULE,
} from "./config"; } from "./config";
import { getSetting, getRules, matchRule } from "./libs"; import { getSettingWithDefault, getRulesWithDefault } from "./libs/storage";
import { Translator } from "./libs/translator"; import { Translator } from "./libs/translator";
import { isIframe } from "./libs/iframe"; import { isIframe } from "./libs/iframe";
import { matchRule } from "./libs/rules";
/** /**
* 入口函数 * 入口函数
*/ */
const init = async () => { const init = async () => {
const href = isIframe ? document.referrer : document.location.href; const href = isIframe ? document.referrer : document.location.href;
const setting = await getSetting(); const setting = await getSettingWithDefault();
const rules = await getRules(); const rules = await getRulesWithDefault();
const rule = await matchRule(rules, href, setting); const rule = await matchRule(rules, href, setting);
const translator = new Translator(rule, setting); const translator = new Translator(rule, setting);

View File

@@ -20,11 +20,6 @@ export function AlertProvider({ children }) {
const [severity, setSeverity] = useState("info"); const [severity, setSeverity] = useState("info");
const [message, setMessage] = useState(""); const [message, setMessage] = useState("");
const error = (msg) => showAlert(msg, "error");
const warning = (msg) => showAlert(msg, "warning");
const info = (msg) => showAlert(msg, "info");
const success = (msg) => showAlert(msg, "success");
const showAlert = (msg, type) => { const showAlert = (msg, type) => {
setOpen(true); setOpen(true);
setMessage(msg); setMessage(msg);
@@ -38,6 +33,11 @@ export function AlertProvider({ children }) {
setOpen(false); setOpen(false);
}; };
const error = (msg) => showAlert(msg, "error");
const warning = (msg) => showAlert(msg, "warning");
const info = (msg) => showAlert(msg, "info");
const success = (msg) => showAlert(msg, "success");
return ( return (
<AlertContext.Provider value={{ error, warning, info, success }}> <AlertContext.Provider value={{ error, warning, info, success }}>
{children} {children}

View File

@@ -1,22 +1,19 @@
import { useSetting, useSettingUpdate } from "./Setting"; import { useCallback } from "react";
import { useSetting } from "./Setting";
/** /**
* 深色模式hook * 深色模式hook
* @returns * @returns
*/ */
export function useDarkMode() { export function useDarkMode() {
const setting = useSetting(); const {
return !!setting?.darkMode; setting: { darkMode },
} updateSetting,
} = useSetting();
/** const toggleDarkMode = useCallback(async () => {
* 切换深色模式
* @returns
*/
export function useDarkModeSwitch() {
const darkMode = useDarkMode();
const updateSetting = useSettingUpdate();
return async () => {
await updateSetting({ darkMode: !darkMode }); await updateSetting({ darkMode: !darkMode });
}; }, [darkMode]);
return { darkMode, toggleDarkMode };
} }

View File

@@ -7,7 +7,9 @@ import { useFetch } from "./Fetch";
* @returns * @returns
*/ */
export const useI18n = () => { export const useI18n = () => {
const { uiLang } = useSetting() ?? {}; const {
setting: { uiLang },
} = useSetting();
return (key, defaultText = "") => I18N?.[key]?.[uiLang] ?? defaultText; return (key, defaultText = "") => I18N?.[key]?.[uiLang] ?? defaultText;
}; };

View File

@@ -1,28 +1,33 @@
import { STOKEY_RULES, DEFAULT_SUBRULES_LIST } from "../config"; import { STOKEY_RULES, DEFAULT_RULES } from "../config";
import storage from "../libs/storage"; import { useStorage } from "./Storage";
import { useStorages } from "./Storage";
import { trySyncRules } from "../libs/sync"; import { trySyncRules } from "../libs/sync";
import { useSync } from "./Sync"; import { useSync } from "./Sync";
import { useSetting, useSettingUpdate } from "./Setting";
import { checkRules } from "../libs/rules"; import { checkRules } from "../libs/rules";
import { useCallback } from "react";
/** /**
* 匹配规则增删改查 hook * 规则 hook
* @returns * @returns
*/ */
export function useRules() { export function useRules() {
const storages = useStorages(); const { data: list, save } = useStorage(STOKEY_RULES, DEFAULT_RULES);
const list = storages?.[STOKEY_RULES] || []; const {
const sync = useSync(); sync: { rulesUpdateAt },
updateSync,
} = useSync();
const update = async (rules) => { const updateRules = useCallback(
const updateAt = sync.opt?.rulesUpdateAt ? Date.now() : 0; async (rules) => {
await storage.setObj(STOKEY_RULES, rules); const updateAt = rulesUpdateAt ? Date.now() : 0;
await sync.update({ rulesUpdateAt: updateAt }); await save(rules);
await updateSync({ rulesUpdateAt: updateAt });
trySyncRules(); trySyncRules();
}; },
[rulesUpdateAt]
);
const add = async (rule) => { const add = useCallback(
async (rule) => {
const rules = [...list]; const rules = [...list];
if (rule.pattern === "*") { if (rule.pattern === "*") {
return; return;
@@ -31,77 +36,90 @@ export function useRules() {
return; return;
} }
rules.unshift(rule); rules.unshift(rule);
await update(rules); await updateRules(rules);
}; },
[list, updateRules]
);
const del = async (pattern) => { const del = useCallback(
async (pattern) => {
let rules = [...list]; let rules = [...list];
if (pattern === "*") { if (pattern === "*") {
return; return;
} }
rules = rules.filter((item) => item.pattern !== pattern); rules = rules.filter((item) => item.pattern !== pattern);
await update(rules); await updateRules(rules);
}; },
[list, updateRules]
);
const put = async (pattern, obj) => { const put = useCallback(
async (pattern, obj) => {
const rules = [...list]; const rules = [...list];
if (pattern === "*") { if (pattern === "*") {
obj.pattern = "*"; obj.pattern = "*";
} }
const rule = rules.find((r) => r.pattern === pattern); const rule = rules.find((r) => r.pattern === pattern);
rule && Object.assign(rule, obj); rule && Object.assign(rule, obj);
await update(rules); await updateRules(rules);
}; },
[list, updateRules]
);
const merge = async (newRules) => { const merge = useCallback(
async (newRules) => {
const rules = [...list]; const rules = [...list];
newRules = checkRules(newRules); newRules = checkRules(newRules);
newRules.forEach((newRule) => { newRules.forEach((newRule) => {
const rule = rules.find((oldRule) => oldRule.pattern === newRule.pattern); const rule = rules.find(
(oldRule) => oldRule.pattern === newRule.pattern
);
if (rule) { if (rule) {
Object.assign(rule, newRule); Object.assign(rule, newRule);
} else { } else {
rules.unshift(newRule); rules.unshift(newRule);
} }
}); });
await update(rules); await updateRules(rules);
}; },
[list, updateRules]
);
return { list, add, del, put, merge }; return { list, add, del, put, merge };
} }
/** // /**
* 订阅规则 // * 订阅规则
* @returns // * @returns
*/ // */
export function useSubrules() { // export function useSubrules() {
const setting = useSetting(); // const setting = useSetting();
const updateSetting = useSettingUpdate(); // const updateSetting = useSettingUpdate();
const list = setting?.subrulesList || DEFAULT_SUBRULES_LIST; // const list = setting?.subrulesList || DEFAULT_SUBRULES_LIST;
const select = async (url) => { // const select = async (url) => {
const subrulesList = [...list]; // const subrulesList = [...list];
subrulesList.forEach((item) => { // subrulesList.forEach((item) => {
if (item.url === url) { // if (item.url === url) {
item.selected = true; // item.selected = true;
} else { // } else {
item.selected = false; // item.selected = false;
} // }
}); // });
await updateSetting({ subrulesList }); // await updateSetting({ subrulesList });
}; // };
const add = async (url) => { // const add = async (url) => {
const subrulesList = [...list]; // const subrulesList = [...list];
subrulesList.push({ url }); // subrulesList.push({ url });
await updateSetting({ subrulesList }); // await updateSetting({ subrulesList });
}; // };
const del = async (url) => { // const del = async (url) => {
let subrulesList = [...list]; // let subrulesList = [...list];
subrulesList = subrulesList.filter((item) => item.url !== url); // subrulesList = subrulesList.filter((item) => item.url !== url);
await updateSetting({ subrulesList }); // await updateSetting({ subrulesList });
}; // };
return { list, select, add, del }; // return { list, select, add, del };
} // }

View File

@@ -1,28 +1,79 @@
import { STOKEY_SETTING } from "../config"; import { STOKEY_SETTING, DEFAULT_SETTING } from "../config";
import storage from "../libs/storage"; import { useStorage } from "./Storage";
import { useStorages } from "./Storage";
import { useSync } from "./Sync"; import { useSync } from "./Sync";
import { trySyncSetting } from "../libs/sync"; import { trySyncSetting } from "../libs/sync";
import { createContext, useCallback, useContext } from "react";
const SettingContext = createContext({
setting: null,
updateSetting: async () => {},
});
export function SettingProvider({ children }) {
const { data, update } = useStorage(STOKEY_SETTING, DEFAULT_SETTING);
const {
sync: { settingUpdateAt },
updateSync,
} = useSync();
const updateSetting = useCallback(
async (obj) => {
const updateAt = settingUpdateAt ? Date.now() : 0;
await update(obj);
await updateSync({ settingUpdateAt: updateAt });
trySyncSetting();
},
[settingUpdateAt]
);
return (
<SettingContext.Provider
value={{
setting: data,
updateSetting,
}}
>
{children}
</SettingContext.Provider>
);
}
/** /**
* 设置hook * 设置 hook
* @returns * @returns
*/ */
export function useSetting() { export function useSetting() {
const storages = useStorages(); return useContext(SettingContext);
return storages?.[STOKEY_SETTING];
} }
/** // export function useSetting() {
* 更新设置 // const [setting,setSeting]= useState(null);
* @returns // useEffect(()=>{
*/ // (async ()=>{
export function useSettingUpdate() { // const
const sync = useSync(); // })()
return async (obj) => { // },[])
const updateAt = sync.opt?.settingUpdateAt ? Date.now() : 0; // }
await storage.putObj(STOKEY_SETTING, obj);
await sync.update({ settingUpdateAt: updateAt }); // /**
trySyncSetting(); // * 设置hook
}; // * @returns
} // */
// export function useSetting() {
// const storages = useStorages();
// return storages?.[STOKEY_SETTING];
// }
// /**
// * 更新设置
// * @returns
// */
// export function useSettingUpdate() {
// const sync = useSync();
// return async (obj) => {
// const updateAt = sync.opt?.settingUpdateAt ? Date.now() : 0;
// await storage.putObj(STOKEY_SETTING, obj);
// await sync.update({ settingUpdateAt: updateAt });
// trySyncSetting();
// };
// }

View File

@@ -1,91 +1,115 @@
import { createContext, useContext, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { browser, isExt, isGm, isWeb } from "../libs/browser"; import { storage } from "../libs/storage";
import {
STOKEY_SETTING,
STOKEY_RULES,
STOKEY_SYNC,
DEFAULT_SETTING,
DEFAULT_RULES,
DEFAULT_SYNC,
} from "../config";
import storage from "../libs/storage";
/** export function useStorage(key, defaultVal = null) {
* 默认配置 const [data, setData] = useState(defaultVal);
*/
export const defaultStorage = {
[STOKEY_SETTING]: DEFAULT_SETTING,
[STOKEY_RULES]: DEFAULT_RULES,
[STOKEY_SYNC]: DEFAULT_SYNC,
};
const activeKeys = Object.keys(defaultStorage); const save = useCallback(
async (val) => {
const StoragesContext = createContext(null); setData(val);
await storage.setObj(key, val);
export function StoragesProvider({ children }) {
const [storages, setStorages] = useState(null);
const handleChanged = (changes) => {
if (isWeb || isGm) {
const { key, oldValue, newValue } = changes;
changes = {
[key]: {
oldValue,
newValue,
}, },
}; [key]
} );
const newStorages = {};
Object.entries(changes) const update = useCallback(
.filter( async (obj) => {
([key, { oldValue, newValue }]) => setData((pre) => ({ ...pre, ...obj }));
activeKeys.includes(key) && oldValue !== newValue await storage.putObj(key, obj);
) },
.forEach(([key, { newValue }]) => { [key]
newStorages[key] = JSON.parse(newValue); );
});
if (Object.keys(newStorages).length !== 0) { const remove = useCallback(async () => {
setStorages((pre) => ({ ...pre, ...newStorages })); setData(null);
} await storage.del(key);
}; }, [key]);
useEffect(() => { useEffect(() => {
// 首次从storage同步配置到内存
(async () => { (async () => {
const curStorages = {}; setData(await storage.getObj(key));
for (const key of activeKeys) {
const val = await storage.get(key);
if (val) {
curStorages[key] = JSON.parse(val);
} else {
await storage.setObj(key, defaultStorage[key]);
curStorages[key] = defaultStorage[key];
}
}
setStorages(curStorages);
})(); })();
}, [key]);
// 监听storage并同步到内存中 return { data, save, update, remove };
storage.onChanged(handleChanged);
// 解除监听
return () => {
if (isExt) {
browser.storage.onChanged.removeListener(handleChanged);
} else {
window.removeEventListener("storage", handleChanged);
}
};
}, []);
return (
<StoragesContext.Provider value={storages}>
{children}
</StoragesContext.Provider>
);
} }
export function useStorages() { // /**
return useContext(StoragesContext); // * 默认配置
} // */
// export const defaultStorage = {
// [STOKEY_SETTING]: DEFAULT_SETTING,
// [STOKEY_RULES]: DEFAULT_RULES,
// [STOKEY_SYNC]: DEFAULT_SYNC,
// };
// const activeKeys = Object.keys(defaultStorage);
// const StoragesContext = createContext(null);
// export function StoragesProvider({ children }) {
// const [storages, setStorages] = useState(null);
// const handleChanged = (changes) => {
// if (isWeb || isGm) {
// const { key, oldValue, newValue } = changes;
// changes = {
// [key]: {
// oldValue,
// newValue,
// },
// };
// }
// const newStorages = {};
// Object.entries(changes)
// .filter(
// ([key, { oldValue, newValue }]) =>
// activeKeys.includes(key) && oldValue !== newValue
// )
// .forEach(([key, { newValue }]) => {
// newStorages[key] = JSON.parse(newValue);
// });
// if (Object.keys(newStorages).length !== 0) {
// setStorages((pre) => ({ ...pre, ...newStorages }));
// }
// };
// useEffect(() => {
// // 首次从storage同步配置到内存
// (async () => {
// const curStorages = {};
// for (const key of activeKeys) {
// const val = await storage.get(key);
// if (val) {
// curStorages[key] = JSON.parse(val);
// } else {
// await storage.setObj(key, defaultStorage[key]);
// curStorages[key] = defaultStorage[key];
// }
// }
// setStorages(curStorages);
// })();
// // 监听storage并同步到内存中
// storage.onChanged(handleChanged);
// // 解除监听
// return () => {
// if (isExt) {
// browser.storage.onChanged.removeListener(handleChanged);
// } else {
// window.removeEventListener("storage", handleChanged);
// }
// };
// }, []);
// return (
// <StoragesContext.Provider value={storages}>
// {children}
// </StoragesContext.Provider>
// );
// }
// export function useStorages() {
// return useContext(StoragesContext);
// }

47
src/hooks/SubRules.js Normal file
View File

@@ -0,0 +1,47 @@
import { DEFAULT_SUBRULES_LIST } from "../config";
import { useSetting } from "./Setting";
import { useCallback } from "react";
/**
* 订阅规则
* @returns
*/
export function useSubRules() {
const { data: setting, update: updateSetting } = useSetting();
const list = setting?.subRulesList || DEFAULT_SUBRULES_LIST;
const select = useCallback(
async (url) => {
const subRulesList = [...list];
subRulesList.forEach((item) => {
if (item.url === url) {
item.selected = true;
} else {
item.selected = false;
}
});
await updateSetting({ subRulesList });
},
[list]
);
const add = useCallback(
async (url) => {
const subRulesList = [...list];
subRulesList.push({ url, selected: false });
await updateSetting({ subRulesList });
},
[list]
);
const del = useCallback(
async (url) => {
let subRulesList = [...list];
subRulesList = subRulesList.filter((item) => item.url !== url);
await updateSetting({ subRulesList });
},
[list]
);
return { list, select, add, del };
}

View File

@@ -1,20 +1,22 @@
import { useCallback } from "react"; import { STOKEY_SYNC, DEFAULT_SYNC } from "../config";
import { STOKEY_SYNC } from "../config"; import { useStorage } from "./Storage";
import storage from "../libs/storage";
import { useStorages } from "./Storage";
/** /**
* sync hook * sync hook
* @returns * @returns
*/ */
export function useSync() { export function useSync() {
const storages = useStorages(); const { data, update } = useStorage(STOKEY_SYNC, DEFAULT_SYNC);
const opt = storages?.[STOKEY_SYNC]; return { sync: data, updateSync: update };
const update = useCallback(async (obj) => {
await storage.putObj(STOKEY_SYNC, obj);
}, []);
return {
opt,
update,
};
} }
// export function useSync() {
// const storages = useStorages();
// const opt = storages?.[STOKEY_SYNC];
// const update = useCallback(async (obj) => {
// await storage.putObj(STOKEY_SYNC, obj);
// }, []);
// return {
// opt,
// update,
// };
// }

View File

@@ -9,8 +9,8 @@ import { THEME_DARK, THEME_LIGHT } from "../config";
* @param {*} param0 * @param {*} param0
* @returns * @returns
*/ */
export default function MuiThemeProvider({ children, options }) { export default function Theme({ children, options }) {
const darkMode = useDarkMode(); const { darkMode } = useDarkMode();
const theme = useMemo(() => { const theme = useMemo(() => {
return createTheme({ return createTheme({
palette: { palette: {

View File

@@ -1,15 +1,16 @@
import { useEffect } from "react"; import { useEffect } from "react";
import { useState } from "react"; import { useState } from "react";
import { detectLang } from "../libs"; import { detectLang } from "../libs/browser";
import { apiTranslate } from "../apis"; import { apiTranslate } from "../apis";
/** /**
* 翻译hook * 翻译hook
* @param {*} q * @param {*} q
* @param {*} rule * @param {*} rule
* @param {*} setting
* @returns * @returns
*/ */
export function useTranslate(q, rule) { export function useTranslate(q, rule, setting) {
const [text, setText] = useState(""); const [text, setText] = useState("");
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [sameLang, setSamelang] = useState(false); const [sameLang, setSamelang] = useState(false);
@@ -30,6 +31,7 @@ export function useTranslate(q, rule) {
q, q,
fromLang, fromLang,
toLang, toLang,
setting,
}); });
setText(trText); setText(trText);
setSamelang(isSame); setSamelang(isSame);
@@ -40,7 +42,7 @@ export function useTranslate(q, rule) {
setLoading(false); setLoading(false);
} }
})(); })();
}, [q, translator, fromLang, toLang]); }, [q, translator, fromLang, toLang, setting]);
return { text, sameLang, loading }; return { text, sameLang, loading };
} }

View File

@@ -1,4 +1,4 @@
import { CLIENT_EXTS, CLIENT_USERSCRIPT, CLIENT_WEB } from "../config"; // import { CLIENT_EXTS, CLIENT_USERSCRIPT, CLIENT_WEB } from "../config";
/** /**
* 浏览器兼容插件,另可用于判断是插件模式还是网页模式,方便开发 * 浏览器兼容插件,另可用于判断是插件模式还是网页模式,方便开发
@@ -13,7 +13,21 @@ function _browser() {
} }
export const browser = _browser(); export const browser = _browser();
export const client = process.env.REACT_APP_CLIENT; // export const client = process.env.REACT_APP_CLIENT;
export const isExt = CLIENT_EXTS.includes(client); // export const isExt = CLIENT_EXTS.includes(client);
export const isGm = client === CLIENT_USERSCRIPT; // export const isGm = client === CLIENT_USERSCRIPT;
export const isWeb = client === CLIENT_WEB; // export const isWeb = client === CLIENT_WEB;
/**
* 本地语言识别
* @param {*} q
* @returns
*/
export const detectLang = async (q) => {
try {
const res = await browser?.i18n?.detectLanguage(q);
return res?.languages?.[0]?.language;
} catch (err) {
console.log("[detect lang]", err);
}
};

6
src/libs/client.js Normal file
View File

@@ -0,0 +1,6 @@
import { CLIENT_EXTS, CLIENT_USERSCRIPT, CLIENT_WEB } from "../config";
export const client = process.env.REACT_APP_CLIENT;
export const isExt = CLIENT_EXTS.includes(client);
export const isGm = client === CLIENT_USERSCRIPT;
export const isWeb = client === CLIENT_WEB;

View File

@@ -1,4 +1,4 @@
import { isExt, isGm } from "./browser"; import { isExt, isGm } from "./client";
import { sendMsg } from "./msg"; import { sendMsg } from "./msg";
import { taskPool } from "./pool"; import { taskPool } from "./pool";
import { import {

View File

@@ -1,108 +1,12 @@
import storage from "./storage"; import { CACHE_NAME } from "../config";
import {
DEFAULT_SETTING,
STOKEY_SETTING,
STOKEY_RULES,
STOKEY_FAB,
GLOBLA_RULE,
GLOBAL_KEY,
DEFAULT_SUBRULES_LIST,
} from "../config";
import { browser } from "./browser";
import { isMatch } from "./utils";
import { loadSubRules } from "./rules";
/** /**
* 查询storage中的设置 * 清除缓存数据
* @returns
*/ */
export const getSetting = async () => ({ export const tryClearCaches = async () => {
...DEFAULT_SETTING,
...((await storage.getObj(STOKEY_SETTING)) || {}),
});
/**
* 查询规则列表
* @returns
*/
export const getRules = async () => (await storage.getObj(STOKEY_RULES)) || [];
/**
* 查询fab位置信息
* @returns
*/
export const getFab = async () => (await storage.getObj(STOKEY_FAB)) || {};
/**
* 设置fab位置信息
* @returns
*/
export const setFab = async (obj) => await storage.setObj(STOKEY_FAB, obj);
/**
* 根据href匹配规则
* @param {*} rules
* @param {string} href
* @returns
*/
export const matchRule = async (
rules,
href,
{ injectRules = true, subrulesList = DEFAULT_SUBRULES_LIST }
) => {
rules = [...rules];
if (injectRules) {
try { try {
const selectedSub = subrulesList.find((item) => item.selected); caches.delete(CACHE_NAME);
if (selectedSub?.url) {
const subRules = await loadSubRules(selectedSub.url);
rules.splice(-1, 0, ...subRules);
}
} catch (err) { } catch (err) {
console.log("[load injectRules]", err); console.log("[clean caches]", err.message);
}
}
const rule = rules.find((r) =>
r.pattern.split(",").some((p) => isMatch(href, p.trim()))
);
const globalRule =
rules.find((r) => r.pattern.split(",").some((p) => p.trim() === "*")) ||
GLOBLA_RULE;
if (!rule) {
return globalRule;
}
rule.selector =
rule?.selector?.trim() ||
globalRule?.selector?.trim() ||
GLOBLA_RULE.selector;
rule.bgColor = rule?.bgColor?.trim() || globalRule?.bgColor?.trim();
["translator", "fromLang", "toLang", "textStyle", "transOpen"].forEach(
(key) => {
if (rule[key] === GLOBAL_KEY) {
rule[key] = globalRule[key];
}
}
);
return rule;
};
/**
* 本地语言识别
* @param {*} q
* @returns
*/
export const detectLang = async (q) => {
try {
const res = await browser?.i18n?.detectLanguage(q);
return res?.languages?.[0]?.language;
} catch (err) {
console.log("[detect lang]", err);
} }
}; };

View File

@@ -1,18 +1,78 @@
import storage from "./storage";
import { fetchPolyfill } from "./fetch";
import { matchValue, type } from "./utils";
import { import {
STOKEY_RULESCACHE_PREFIX, getSyncWithDefault,
updateSync,
getSubRulesWithDefault,
getSubRules,
delSubRules,
setSubRules,
} from "./storage";
import { fetchPolyfill } from "./fetch";
import { matchValue, type, isMatch } from "./utils";
import {
GLOBAL_KEY, GLOBAL_KEY,
OPT_TRANS_ALL, OPT_TRANS_ALL,
OPT_STYLE_ALL, OPT_STYLE_ALL,
OPT_LANGS_FROM, OPT_LANGS_FROM,
OPT_LANGS_TO, OPT_LANGS_TO,
GLOBLA_RULE,
DEFAULT_SUBRULES_LIST,
} from "../config"; } from "../config";
import { syncOpt } from "./sync";
const fromLangs = OPT_LANGS_FROM.map((item) => item[0]); // import { syncOpt } from "./sync";
const toLangs = OPT_LANGS_TO.map((item) => item[0]);
/**
* 根据href匹配规则
* @param {*} rules
* @param {string} href
* @returns
*/
export const matchRule = async (
rules,
href,
{ injectRules = true, subrulesList = DEFAULT_SUBRULES_LIST }
) => {
rules = [...rules];
if (injectRules) {
try {
const selectedSub = subrulesList.find((item) => item.selected);
if (selectedSub?.url) {
const subRules = await loadSubRules(selectedSub.url);
rules.splice(-1, 0, ...subRules);
}
} catch (err) {
console.log("[load injectRules]", err);
}
}
const rule = rules.find((r) =>
r.pattern.split(",").some((p) => isMatch(href, p.trim()))
);
const globalRule =
rules.find((r) => r.pattern.split(",").some((p) => p.trim() === "*")) ||
GLOBLA_RULE;
if (!rule) {
return globalRule;
}
rule.selector =
rule?.selector?.trim() ||
globalRule?.selector?.trim() ||
GLOBLA_RULE.selector;
rule.bgColor = rule?.bgColor?.trim() || globalRule?.bgColor?.trim();
["translator", "fromLang", "toLang", "textStyle", "transOpen"].forEach(
(key) => {
if (rule[key] === GLOBAL_KEY) {
rule[key] = globalRule[key];
}
}
);
return rule;
};
/** /**
* 检查过滤rules * 检查过滤rules
@@ -27,6 +87,8 @@ export const checkRules = (rules) => {
throw new Error("data error"); throw new Error("data error");
} }
const fromLangs = OPT_LANGS_FROM.map((item) => item[0]);
const toLangs = OPT_LANGS_TO.map((item) => item[0]);
const patternSet = new Set(); const patternSet = new Set();
rules = rules rules = rules
.filter((rule) => type(rule) === "object") .filter((rule) => type(rule) === "object")
@@ -62,84 +124,18 @@ export const checkRules = (rules) => {
return rules; return rules;
}; };
/** // /**
* 订阅规则的本地缓存 // * 订阅规则的本地缓存
*/ // */
export const rulesCache = { // export const rulesCache = {
fetch: async (url, isBg = false) => { // fetch: async (url, isBg = false) => {
const res = await fetchPolyfill(url, { isBg }); // const res = await fetchPolyfill(url, { isBg });
const rules = checkRules(res).filter( // const rules = checkRules(res).filter(
(rule) => rule.pattern.replaceAll(GLOBAL_KEY, "") !== "" // (rule) => rule.pattern.replaceAll(GLOBAL_KEY, "") !== ""
); // );
return rules; // return rules;
}, // },
set: async (url, rules) => { // set: (url, rules) => setSubRules(url, rules),
await storage.setObj(`${STOKEY_RULESCACHE_PREFIX}${url}`, rules); // get: (url) => getSubRulesWithDefault(url),
}, // del: (url) => delSubRules(url),
get: async (url) => { // };
return await storage.getObj(`${STOKEY_RULESCACHE_PREFIX}${url}`);
},
del: async (url) => {
await storage.del(`${STOKEY_RULESCACHE_PREFIX}${url}`);
},
};
/**
* 同步订阅规则
* @param {*} url
* @returns
*/
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;
}
return await syncSubRules(url);
};

View File

@@ -1,28 +1,41 @@
import { browser, isExt, isGm } from "./browser"; import {
STOKEY_SETTING,
STOKEY_RULES,
STOKEY_FAB,
STOKEY_SYNC,
STOKEY_MSAUTH,
STOKEY_RULESCACHE_PREFIX,
DEFAULT_SETTING,
DEFAULT_RULES,
DEFAULT_SYNC,
BUILTIN_RULES,
} from "../config";
import { browser, isExt, isGm } from "./client";
// import { APP_NAME } from "../config/app";
async function set(key, val) { async function set(key, val) {
if (isExt) { if (isExt) {
await browser.storage.local.set({ [key]: val }); await browser.storage.local.set({ [key]: val });
} else if (isGm) { } else if (isGm) {
const oldValue = await (window.KISS_GM || GM).getValue(key); // const oldValue = await (window.KISS_GM || GM).getValue(key);
await (window.KISS_GM || GM).setValue(key, val); await (window.KISS_GM || GM).setValue(key, val);
window.dispatchEvent( // window.dispatchEvent(
new StorageEvent("storage", { // new StorageEvent("storage", {
key, // key,
oldValue, // oldValue,
newValue: val, // newValue: val,
}) // })
); // );
} else { } else {
const oldValue = window.localStorage.getItem(key); // const oldValue = window.localStorage.getItem(key);
window.localStorage.setItem(key, val); window.localStorage.setItem(key, val);
window.dispatchEvent( // window.dispatchEvent(
new StorageEvent("storage", { // new StorageEvent("storage", {
key, // key,
oldValue, // oldValue,
newValue: val, // newValue: val,
}) // })
); // );
} }
} }
@@ -41,25 +54,25 @@ async function del(key) {
if (isExt) { if (isExt) {
await browser.storage.local.remove([key]); await browser.storage.local.remove([key]);
} else if (isGm) { } else if (isGm) {
const oldValue = await (window.KISS_GM || GM).getValue(key); // const oldValue = await (window.KISS_GM || GM).getValue(key);
await (window.KISS_GM || GM).deleteValue(key); await (window.KISS_GM || GM).deleteValue(key);
window.dispatchEvent( // window.dispatchEvent(
new StorageEvent("storage", { // new StorageEvent("storage", {
key, // key,
oldValue, // oldValue,
newValue: null, // newValue: null,
}) // })
); // );
} else { } else {
const oldValue = window.localStorage.getItem(key); // const oldValue = window.localStorage.getItem(key);
window.localStorage.removeItem(key); window.localStorage.removeItem(key);
window.dispatchEvent( // window.dispatchEvent(
new StorageEvent("storage", { // new StorageEvent("storage", {
key, // key,
oldValue, // oldValue,
newValue: null, // newValue: null,
}) // })
); // );
} }
} }
@@ -83,22 +96,22 @@ async function putObj(key, obj) {
await setObj(key, { ...cur, ...obj }); await setObj(key, { ...cur, ...obj });
} }
/** // /**
* 监听storage事件 // * 监听storage事件
* @param {*} handleChanged // * @param {*} handleChanged
*/ // */
function onChanged(handleChanged) { // function onChanged(handleChanged) {
if (isExt) { // if (isExt) {
browser.storage.onChanged.addListener(handleChanged); // browser.storage.onChanged.addListener(handleChanged);
} else { // } else {
window.addEventListener("storage", handleChanged); // window.addEventListener("storage", handleChanged);
} // }
} // }
/** /**
* 对storage的封装 * 对storage的封装
*/ */
const storage = { export const storage = {
get, get,
set, set,
del, del,
@@ -106,7 +119,70 @@ const storage = {
trySetObj, trySetObj,
getObj, getObj,
putObj, putObj,
onChanged, // onChanged,
}; };
export default storage; /**
* 设置信息
*/
export const getSetting = () => getObj(STOKEY_SETTING);
export const getSettingWithDefault = async () => ({
...DEFAULT_SETTING,
...((await getSetting()) || {}),
});
export const setSetting = (val) => setObj(STOKEY_SETTING, val);
export const updateSetting = (obj) => putObj(STOKEY_SETTING, obj);
/**
* 规则列表
*/
export const getRules = () => getObj(STOKEY_RULES);
export const getRulesWithDefault = async () =>
(await getRules()) || DEFAULT_RULES;
export const setRules = (val) => setObj(STOKEY_RULES, val);
/**
* 订阅规则
*/
export const getSubRules = (url) => getObj(STOKEY_RULESCACHE_PREFIX + url);
export const getSubRulesWithDefault = async () => (await getSubRules()) || [];
export const delSubRules = (url) => del(STOKEY_RULESCACHE_PREFIX + url);
export const setSubRules = (url, val) =>
setObj(STOKEY_RULESCACHE_PREFIX + url, val);
/**
* fab位置
*/
export const getFab = () => getObj(STOKEY_FAB);
export const getFabWithDefault = async () => (await getFab()) || {};
export const setFab = (obj) => setObj(STOKEY_FAB, obj);
/**
* 数据同步
*/
export const getSync = () => getObj(STOKEY_SYNC);
export const getSyncWithDefault = async () => (await getSync()) || DEFAULT_SYNC;
export const updateSync = (obj) => putObj(STOKEY_SYNC, obj);
/**
* ms auth
*/
export const getMsauth = () => getObj(STOKEY_MSAUTH);
export const setMsauth = (val) => setObj(STOKEY_MSAUTH, val);
/**
* 存入默认数据
*/
export const tryInitDefaultData = async () => {
try {
await trySetObj(STOKEY_SETTING, DEFAULT_SETTING);
await trySetObj(STOKEY_RULES, DEFAULT_RULES);
await trySetObj(STOKEY_SYNC, DEFAULT_SYNC);
await trySetObj(
`${STOKEY_RULESCACHE_PREFIX}${process.env.REACT_APP_RULESURL}`,
BUILTIN_RULES
);
} catch (err) {
console.log("[init default]", err);
}
};

62
src/libs/subRules.js Normal file
View File

@@ -0,0 +1,62 @@
import { getSyncWithDefault, updateSync } from "./storage";
import { apiFetchRules } from "../apis";
/**
* 同步订阅规则
* @param {*} url
* @returns
*/
export const syncSubRules = async (url, isBg = false) => {
const rules = await apiFetchRules(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 getSyncWithDefault();
const now = Date.now();
const interval = 24 * 60 * 60 * 1000; // 间隔一天
if (now - subRulesSyncAt > interval) {
await syncAllSubRules(subrulesList, isBg);
await updateSync({ subRulesSyncAt: now });
}
} catch (err) {
console.log("[try sync all subrules]", err);
}
};
/**
* 从缓存或远程加载订阅规则
* @param {*} url
* @returns
*/
export const loadOrFetchSubRules = async (url) => {
const rules = await apiFetchRules(url);
if (rules?.length) {
return rules;
}
return await syncSubRules(url);
};

View File

@@ -8,27 +8,27 @@ import {
STOKEY_RULES, STOKEY_RULES,
KV_SALT_SHARE, KV_SALT_SHARE,
} from "../config"; } from "../config";
import storage from "../libs/storage"; import { storage, getSyncWithDefault, updateSync } from "../libs/storage";
import { getSetting, getRules } from "."; import { getSetting, getRules } from ".";
import { apiSyncData } from "../apis"; import { apiSyncData } from "../apis";
import { sha256 } from "./utils"; import { sha256 } from "./utils";
/** // /**
* 同步相关数据 // * 同步相关数据
*/ // */
export const syncOpt = { // export const syncOpt = {
load: async () => (await storage.getObj(STOKEY_SYNC)) || DEFAULT_SYNC, // load: async () => (await storage.getObj(STOKEY_SYNC)) || DEFAULT_SYNC,
update: async (obj) => { // update: async (obj) => {
await storage.putObj(STOKEY_SYNC, obj); // await storage.putObj(STOKEY_SYNC, obj);
}, // },
}; // };
/** /**
* 同步设置 * 同步设置
* @returns * @returns
*/ */
export const syncSetting = async (isBg = false) => { const syncSetting = async (isBg = false) => {
const { syncUrl, syncKey, settingUpdateAt } = await syncOpt.load(); const { syncUrl, syncKey, settingUpdateAt } = await getSyncWithDefault();
if (!syncUrl || !syncKey) { if (!syncUrl || !syncKey) {
return; return;
} }
@@ -46,13 +46,13 @@ export const syncSetting = async (isBg = false) => {
); );
if (res && res.updateAt > settingUpdateAt) { if (res && res.updateAt > settingUpdateAt) {
await syncOpt.update({ await updateSync({
settingUpdateAt: res.updateAt, settingUpdateAt: res.updateAt,
settingSyncAt: res.updateAt, settingSyncAt: res.updateAt,
}); });
await storage.setObj(STOKEY_SETTING, res.value); await storage.setObj(STOKEY_SETTING, res.value);
} else { } else {
await syncOpt.update({ settingSyncAt: res.updateAt }); await updateSync({ settingSyncAt: res.updateAt });
} }
}; };
@@ -68,8 +68,8 @@ export const trySyncSetting = async (isBg = false) => {
* 同步规则 * 同步规则
* @returns * @returns
*/ */
export const syncRules = async (isBg = false) => { const syncRules = async (isBg = false) => {
const { syncUrl, syncKey, rulesUpdateAt } = await syncOpt.load(); const { syncUrl, syncKey, rulesUpdateAt } = await getSyncWithDefault();
if (!syncUrl || !syncKey) { if (!syncUrl || !syncKey) {
return; return;
} }
@@ -87,13 +87,13 @@ export const syncRules = async (isBg = false) => {
); );
if (res && res.updateAt > rulesUpdateAt) { if (res && res.updateAt > rulesUpdateAt) {
await syncOpt.update({ await updateSync({
rulesUpdateAt: res.updateAt, rulesUpdateAt: res.updateAt,
rulesSyncAt: res.updateAt, rulesSyncAt: res.updateAt,
}); });
await storage.setObj(STOKEY_RULES, res.value); await storage.setObj(STOKEY_RULES, res.value);
} else { } else {
await syncOpt.update({ rulesSyncAt: res.updateAt }); await updateSync({ rulesSyncAt: res.updateAt });
} }
}; };

View File

@@ -12,14 +12,16 @@ import {
import Content from "../views/Content"; import Content from "../views/Content";
import { fetchUpdate, fetchClear } from "./fetch"; import { fetchUpdate, fetchClear } from "./fetch";
import { debounce } from "./utils"; import { debounce } from "./utils";
import { isExt } from "./client";
/** /**
* 翻译类 * 翻译类
*/ */
export class Translator { export class Translator {
_rule = {}; _rule = {};
_minLength = 0; _setting = {};
_maxLength = 0; _rootNodes = new Set();
_tranNodes = new Map();
_skipNodeNames = [ _skipNodeNames = [
APP_LCNAME, APP_LCNAME,
"style", "style",
@@ -36,8 +38,6 @@ export class Translator {
"script", "script",
"iframe", "iframe",
]; ];
_rootNodes = new Set();
_tranNodes = new Map();
// 显示 // 显示
_interseObserver = new IntersectionObserver( _interseObserver = new IntersectionObserver(
@@ -89,12 +89,14 @@ export class Translator {
}; };
}; };
constructor(rule, { fetchInterval, fetchLimit, minLength, maxLength }) { constructor(rule, setting) {
const { fetchInterval, fetchLimit } = setting;
fetchUpdate(fetchInterval, fetchLimit); fetchUpdate(fetchInterval, fetchLimit);
this._overrideAttachShadow(); this._overrideAttachShadow();
this._minLength = minLength ?? TRANS_MIN_LENGTH;
this._maxLength = maxLength ?? TRANS_MAX_LENGTH; this._setting = setting;
this.rule = rule; this._rule = rule;
if (rule.transOpen === "true") { if (rule.transOpen === "true") {
this._register(); this._register();
} }
@@ -268,7 +270,11 @@ export class Translator {
this._tranNodes.set(el, q); this._tranNodes.set(el, q);
// 太长或太短 // 太长或太短
if (!q || q.length < this._minLength || q.length > this._maxLength) { if (
!q ||
q.length < (this._setting.minLength ?? TRANS_MIN_LENGTH) ||
q.length > (this._setting.maxLength ?? TRANS_MAX_LENGTH)
) {
return; return;
} }

View File

@@ -1,16 +1,16 @@
import React from "react"; import React from "react";
import ReactDOM from "react-dom/client"; import ReactDOM from "react-dom/client";
import { StoragesProvider } from "./hooks/Storage"; import { SettingProvider } from "./hooks/Setting";
import ThemeProvider from "./hooks/Theme"; import ThemeProvider from "./hooks/Theme";
import Popup from "./views/Popup"; import Popup from "./views/Popup";
const root = ReactDOM.createRoot(document.getElementById("root")); const root = ReactDOM.createRoot(document.getElementById("root"));
root.render( root.render(
<React.StrictMode> <React.StrictMode>
<StoragesProvider> <SettingProvider>
<ThemeProvider> <ThemeProvider>
<Popup /> <Popup />
</ThemeProvider> </ThemeProvider>
</StoragesProvider> </SettingProvider>
</React.StrictMode> </React.StrictMode>
); );

View File

@@ -5,8 +5,8 @@ import createCache from "@emotion/cache";
import { CacheProvider } from "@emotion/react"; import { CacheProvider } from "@emotion/react";
import { getSetting, getRules, matchRule, getFab } from "./libs"; import { getSetting, getRules, matchRule, getFab } from "./libs";
import { Translator } from "./libs/translator"; import { Translator } from "./libs/translator";
import { trySyncAllSubRules } from "./libs/rules"; import { trySyncAllSubRules } from "./libs/subRules";
import { isGm } from "./libs/browser"; import { isGm } from "./libs/client";
import { MSG_TRANS_TOGGLE, MSG_TRANS_PUTRULE } from "./config"; import { MSG_TRANS_TOGGLE, MSG_TRANS_PUTRULE } from "./config";
import { isIframe } from "./libs/iframe"; import { isIframe } from "./libs/iframe";
import { handlePing, injectScript } from "./libs/gm"; import { handlePing, injectScript } from "./libs/gm";

View File

@@ -1,7 +1,7 @@
import { useCallback, useEffect, useMemo, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import { limitNumber } from "../../libs/utils"; import { limitNumber } from "../../libs/utils";
import { isMobile } from "../../libs/mobile"; import { isMobile } from "../../libs/mobile";
import { setFab } from "../../libs"; import { setFab } from "../../libs/storage";
const getEdgePosition = ( const getEdgePosition = (
{ x: left, y: top, edge }, { x: left, y: top, edge },

View File

@@ -8,7 +8,7 @@ import IconButton from "@mui/material/IconButton";
import CloseIcon from "@mui/icons-material/Close"; import CloseIcon from "@mui/icons-material/Close";
import Stack from "@mui/material/Stack"; import Stack from "@mui/material/Stack";
import { useEffect, useState, useMemo, useCallback } from "react"; import { useEffect, useState, useMemo, useCallback } from "react";
import { StoragesProvider } from "../../hooks/Storage"; import { SettingProvider } from "../../hooks/Setting";
import Popup from "../Popup"; import Popup from "../Popup";
import { debounce } from "../../libs/utils"; import { debounce } from "../../libs/utils";
@@ -81,7 +81,7 @@ export default function Action({ translator, fab }) {
}; };
return ( return (
<StoragesProvider> <SettingProvider>
<ThemeProvider> <ThemeProvider>
<Draggable <Draggable
key="pop" key="pop"
@@ -139,6 +139,6 @@ export default function Action({ translator, fab }) {
} }
/> />
</ThemeProvider> </ThemeProvider>
</StoragesProvider> </SettingProvider>
); );
} }

View File

@@ -16,7 +16,7 @@ import { useTranslate } from "../../hooks/Translate";
export default function Content({ q, translator }) { export default function Content({ q, translator }) {
const [rule, setRule] = useState(translator.rule); const [rule, setRule] = useState(translator.rule);
const [hover, setHover] = useState(false); const [hover, setHover] = useState(false);
const { text, sameLang, loading } = useTranslate(q, rule); const { text, sameLang, loading } = useTranslate(q, rule, translator.setting);
const { textStyle, bgColor } = rule; const { textStyle, bgColor } = rule;
const handleMouseEnter = () => { const handleMouseEnter = () => {

View File

@@ -4,7 +4,6 @@ import IconButton from "@mui/material/IconButton";
import MenuIcon from "@mui/icons-material/Menu"; import MenuIcon from "@mui/icons-material/Menu";
import Toolbar from "@mui/material/Toolbar"; import Toolbar from "@mui/material/Toolbar";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import { useDarkModeSwitch } from "../../hooks/ColorMode";
import { useDarkMode } from "../../hooks/ColorMode"; import { useDarkMode } from "../../hooks/ColorMode";
import LightModeIcon from "@mui/icons-material/LightMode"; import LightModeIcon from "@mui/icons-material/LightMode";
import DarkModeIcon from "@mui/icons-material/DarkMode"; import DarkModeIcon from "@mui/icons-material/DarkMode";
@@ -14,8 +13,7 @@ import { useI18n } from "../../hooks/I18n";
function Header(props) { function Header(props) {
const i18n = useI18n(); const i18n = useI18n();
const { onDrawerToggle } = props; const { onDrawerToggle } = props;
const switchColorMode = useDarkModeSwitch(); const { darkMode, toggleDarkMode } = useDarkMode();
const darkMode = useDarkMode();
return ( return (
<AppBar <AppBar
@@ -43,7 +41,7 @@ function Header(props) {
href={process.env.REACT_APP_HOMEPAGE} href={process.env.REACT_APP_HOMEPAGE}
>{`${i18n("app_name")} v${process.env.REACT_APP_VERSION}`}</Link> >{`${i18n("app_name")} v${process.env.REACT_APP_VERSION}`}</Link>
</Box> </Box>
<IconButton onClick={switchColorMode} color="inherit"> <IconButton onClick={toggleDarkMode} color="inherit">
{darkMode ? <LightModeIcon /> : <DarkModeIcon />} {darkMode ? <LightModeIcon /> : <DarkModeIcon />}
</IconButton> </IconButton>
</Toolbar> </Toolbar>

View File

@@ -5,7 +5,7 @@ import TextField from "@mui/material/TextField";
import MenuItem from "@mui/material/MenuItem"; import MenuItem from "@mui/material/MenuItem";
import FormControl from "@mui/material/FormControl"; import FormControl from "@mui/material/FormControl";
import Select from "@mui/material/Select"; import Select from "@mui/material/Select";
import { useSetting, useSettingUpdate } from "../../hooks/Setting"; import { useSetting } from "../../hooks/Setting";
import { limitNumber, debounce } from "../../libs/utils"; import { limitNumber, debounce } from "../../libs/utils";
import { useI18n } from "../../hooks/I18n"; import { useI18n } from "../../hooks/I18n";
import { UI_LANGS } from "../../config"; import { UI_LANGS } from "../../config";
@@ -13,8 +13,7 @@ import { useMemo } from "react";
export default function Settings() { export default function Settings() {
const i18n = useI18n(); const i18n = useI18n();
const setting = useSetting(); const { setting, updateSetting } = useSetting();
const updateSetting = useSettingUpdate();
const handleChange = useMemo( const handleChange = useMemo(
() => () =>

View File

@@ -4,10 +4,10 @@ import Rules from "./Rules";
import Setting from "./Setting"; import Setting from "./Setting";
import Layout from "./Layout"; import Layout from "./Layout";
import SyncSetting from "./SyncSetting"; import SyncSetting from "./SyncSetting";
import { StoragesProvider } from "../../hooks/Storage"; import { SettingProvider } from "../../hooks/Setting";
import ThemeProvider from "../../hooks/Theme"; import ThemeProvider from "../../hooks/Theme";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { isGm } from "../../libs/browser"; import { isGm } from "../../libs/client";
import { sleep } from "../../libs/utils"; import { sleep } from "../../libs/utils";
import CircularProgress from "@mui/material/CircularProgress"; import CircularProgress from "@mui/material/CircularProgress";
import { trySyncAll } from "../../libs/sync"; import { trySyncAll } from "../../libs/sync";
@@ -89,7 +89,7 @@ export default function Options() {
} }
return ( return (
<StoragesProvider> <SettingProvider>
<ThemeProvider> <ThemeProvider>
<AlertProvider> <AlertProvider>
<HashRouter> <HashRouter>
@@ -104,6 +104,6 @@ export default function Options() {
</HashRouter> </HashRouter>
</AlertProvider> </AlertProvider>
</ThemeProvider> </ThemeProvider>
</StoragesProvider> </SettingProvider>
); );
} }

View File

@@ -6,7 +6,8 @@ import FormControlLabel from "@mui/material/FormControlLabel";
import Switch from "@mui/material/Switch"; import Switch from "@mui/material/Switch";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import { sendTabMsg } from "../../libs/msg"; import { sendTabMsg } from "../../libs/msg";
import { browser, isExt } from "../../libs/browser"; import { browser } from "../../libs/browser";
import { isExt } from "../../libs/client";
import { useI18n } from "../../hooks/I18n"; import { useI18n } from "../../hooks/I18n";
import TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";
import { import {