dev...
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import queryString from "query-string";
|
||||
import { fetchPolyfill } from "../libs/fetch";
|
||||
import {
|
||||
GLOBAL_KEY,
|
||||
OPT_TRANS_GOOGLE,
|
||||
OPT_TRANS_MICROSOFT,
|
||||
OPT_TRANS_OPENAI,
|
||||
@@ -10,8 +11,9 @@ import {
|
||||
PROMPT_PLACE_TO,
|
||||
KV_SALT_SYNC,
|
||||
} from "../config";
|
||||
import { getSetting, detectLang } from "../libs";
|
||||
import { detectLang } from "../libs/browser";
|
||||
import { sha256 } from "../libs/utils";
|
||||
import { checkRules } from "../libs/rules";
|
||||
|
||||
/**
|
||||
* 同步数据
|
||||
@@ -31,6 +33,20 @@ export const apiSyncData = async (url, key, data, isBg = false) =>
|
||||
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
|
||||
@@ -38,7 +54,8 @@ export const apiSyncData = async (url, key, data, isBg = false) =>
|
||||
* @param {*} from
|
||||
* @returns
|
||||
*/
|
||||
const apiGoogleTranslate = async (translator, text, to, from) => {
|
||||
const apiGoogleTranslate = async (translator, text, to, from, setting) => {
|
||||
const { googleUrl } = setting;
|
||||
const params = {
|
||||
client: "gtx",
|
||||
dt: "t",
|
||||
@@ -48,7 +65,6 @@ const apiGoogleTranslate = async (translator, text, to, from) => {
|
||||
tl: to,
|
||||
q: text,
|
||||
};
|
||||
const { googleUrl } = await getSetting();
|
||||
const input = `${googleUrl}?${queryString.stringify(params)}`;
|
||||
return fetchPolyfill(input, {
|
||||
headers: {
|
||||
@@ -93,9 +109,8 @@ const apiMicrosoftTranslate = (translator, text, to, from) => {
|
||||
* @param {*} from
|
||||
* @returns
|
||||
*/
|
||||
const apiOpenaiTranslate = async (translator, text, to, from) => {
|
||||
const { openaiUrl, openaiKey, openaiModel, openaiPrompt } =
|
||||
await getSetting();
|
||||
const apiOpenaiTranslate = async (translator, text, to, from, setting) => {
|
||||
const { openaiUrl, openaiKey, openaiModel, openaiPrompt } = setting;
|
||||
let prompt = openaiPrompt
|
||||
.replaceAll(PROMPT_PLACE_FROM, from)
|
||||
.replaceAll(PROMPT_PLACE_TO, to);
|
||||
@@ -131,7 +146,13 @@ const apiOpenaiTranslate = async (translator, text, to, from) => {
|
||||
* @param {*} param0
|
||||
* @returns
|
||||
*/
|
||||
export const apiTranslate = async ({ translator, q, fromLang, toLang }) => {
|
||||
export const apiTranslate = async ({
|
||||
translator,
|
||||
q,
|
||||
fromLang,
|
||||
toLang,
|
||||
setting,
|
||||
}) => {
|
||||
let trText = "";
|
||||
let isSame = false;
|
||||
|
||||
@@ -139,7 +160,7 @@ export const apiTranslate = async ({ translator, q, fromLang, toLang }) => {
|
||||
let to = OPT_LANGS_SPECIAL?.[translator]?.get(toLang) ?? toLang;
|
||||
|
||||
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(" ");
|
||||
isSame = to === res.src;
|
||||
} else if (translator === OPT_TRANS_MICROSOFT) {
|
||||
@@ -147,9 +168,11 @@ export const apiTranslate = async ({ translator, q, fromLang, toLang }) => {
|
||||
trText = res[0].translations[0].text;
|
||||
isSame = to === res[0].detectedLanguage.language;
|
||||
} 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;
|
||||
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];
|
||||
|
||||
@@ -7,35 +7,19 @@ import {
|
||||
MSG_TRANS_TOGGLE_STYLE,
|
||||
CMD_TOGGLE_TRANSLATE,
|
||||
CMD_TOGGLE_STYLE,
|
||||
DEFAULT_SETTING,
|
||||
DEFAULT_RULES,
|
||||
DEFAULT_SYNC,
|
||||
STOKEY_SETTING,
|
||||
STOKEY_RULES,
|
||||
STOKEY_SYNC,
|
||||
CACHE_NAME,
|
||||
STOKEY_RULESCACHE_PREFIX,
|
||||
BUILTIN_RULES,
|
||||
} from "./config";
|
||||
import storage from "./libs/storage";
|
||||
import { getSetting } from "./libs";
|
||||
import { getSettingWithDefault, tryInitDefaultData } from "./libs/storage";
|
||||
import { trySyncAll } from "./libs/sync";
|
||||
import { fetchData, fetchPool } from "./libs/fetch";
|
||||
import { sendTabMsg } from "./libs/msg";
|
||||
import { trySyncAllSubRules } from "./libs/rules";
|
||||
import { trySyncAllSubRules } from "./libs/subRules";
|
||||
import { tryClearCaches } from "./libs";
|
||||
|
||||
/**
|
||||
* 插件安装
|
||||
*/
|
||||
browser.runtime.onInstalled.addListener(() => {
|
||||
console.log("KISS Translator onInstalled");
|
||||
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
|
||||
);
|
||||
tryInitDefaultData();
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -48,13 +32,9 @@ browser.runtime.onStartup.addListener(async () => {
|
||||
await trySyncAll(true);
|
||||
|
||||
// 清除缓存
|
||||
const setting = await getSetting();
|
||||
const setting = await getSettingWithDefault();
|
||||
if (setting.clearCache) {
|
||||
try {
|
||||
caches.delete(CACHE_NAME);
|
||||
} catch (err) {
|
||||
console.log("[clean caches]", err.message);
|
||||
}
|
||||
tryClearCaches();
|
||||
}
|
||||
|
||||
// 同步订阅规则
|
||||
|
||||
4
src/config/app.js
Normal file
4
src/config/app.js
Normal 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();
|
||||
@@ -5,12 +5,9 @@ import {
|
||||
DEFAULT_RULE,
|
||||
BUILTIN_RULES,
|
||||
} from "./rules";
|
||||
import { APP_NAME, APP_LCNAME } from "./app";
|
||||
export { I18N, UI_LANGS } from "./i18n";
|
||||
export { GLOBAL_KEY, SHADOW_KEY, DEFAULT_RULE, BUILTIN_RULES };
|
||||
|
||||
const APP_NAME = process.env.REACT_APP_NAME.trim().split(/\s+/).join("-");
|
||||
|
||||
export const APP_LCNAME = APP_NAME.toLowerCase();
|
||||
export { GLOBAL_KEY, SHADOW_KEY, DEFAULT_RULE, BUILTIN_RULES, APP_LCNAME };
|
||||
|
||||
export const STOKEY_MSAUTH = `${APP_NAME}_msauth`;
|
||||
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",
|
||||
selected: false,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -5,17 +5,18 @@ import {
|
||||
MSG_TRANS_GETRULE,
|
||||
MSG_TRANS_PUTRULE,
|
||||
} from "./config";
|
||||
import { getSetting, getRules, matchRule } from "./libs";
|
||||
import { getSettingWithDefault, getRulesWithDefault } from "./libs/storage";
|
||||
import { Translator } from "./libs/translator";
|
||||
import { isIframe } from "./libs/iframe";
|
||||
import { matchRule } from "./libs/rules";
|
||||
|
||||
/**
|
||||
* 入口函数
|
||||
*/
|
||||
const init = async () => {
|
||||
const href = isIframe ? document.referrer : document.location.href;
|
||||
const setting = await getSetting();
|
||||
const rules = await getRules();
|
||||
const setting = await getSettingWithDefault();
|
||||
const rules = await getRulesWithDefault();
|
||||
const rule = await matchRule(rules, href, setting);
|
||||
const translator = new Translator(rule, setting);
|
||||
|
||||
|
||||
@@ -20,11 +20,6 @@ export function AlertProvider({ children }) {
|
||||
const [severity, setSeverity] = useState("info");
|
||||
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) => {
|
||||
setOpen(true);
|
||||
setMessage(msg);
|
||||
@@ -38,6 +33,11 @@ export function AlertProvider({ children }) {
|
||||
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 (
|
||||
<AlertContext.Provider value={{ error, warning, info, success }}>
|
||||
{children}
|
||||
|
||||
@@ -1,22 +1,19 @@
|
||||
import { useSetting, useSettingUpdate } from "./Setting";
|
||||
import { useCallback } from "react";
|
||||
import { useSetting } from "./Setting";
|
||||
|
||||
/**
|
||||
* 深色模式hook
|
||||
* @returns
|
||||
*/
|
||||
export function useDarkMode() {
|
||||
const setting = useSetting();
|
||||
return !!setting?.darkMode;
|
||||
}
|
||||
const {
|
||||
setting: { darkMode },
|
||||
updateSetting,
|
||||
} = useSetting();
|
||||
|
||||
/**
|
||||
* 切换深色模式
|
||||
* @returns
|
||||
*/
|
||||
export function useDarkModeSwitch() {
|
||||
const darkMode = useDarkMode();
|
||||
const updateSetting = useSettingUpdate();
|
||||
return async () => {
|
||||
const toggleDarkMode = useCallback(async () => {
|
||||
await updateSetting({ darkMode: !darkMode });
|
||||
};
|
||||
}, [darkMode]);
|
||||
|
||||
return { darkMode, toggleDarkMode };
|
||||
}
|
||||
|
||||
@@ -7,7 +7,9 @@ import { useFetch } from "./Fetch";
|
||||
* @returns
|
||||
*/
|
||||
export const useI18n = () => {
|
||||
const { uiLang } = useSetting() ?? {};
|
||||
const {
|
||||
setting: { uiLang },
|
||||
} = useSetting();
|
||||
return (key, defaultText = "") => I18N?.[key]?.[uiLang] ?? defaultText;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,28 +1,33 @@
|
||||
import { STOKEY_RULES, DEFAULT_SUBRULES_LIST } from "../config";
|
||||
import storage from "../libs/storage";
|
||||
import { useStorages } from "./Storage";
|
||||
import { STOKEY_RULES, DEFAULT_RULES } from "../config";
|
||||
import { useStorage } from "./Storage";
|
||||
import { trySyncRules } from "../libs/sync";
|
||||
import { useSync } from "./Sync";
|
||||
import { useSetting, useSettingUpdate } from "./Setting";
|
||||
import { checkRules } from "../libs/rules";
|
||||
import { useCallback } from "react";
|
||||
|
||||
/**
|
||||
* 匹配规则增删改查 hook
|
||||
* 规则 hook
|
||||
* @returns
|
||||
*/
|
||||
export function useRules() {
|
||||
const storages = useStorages();
|
||||
const list = storages?.[STOKEY_RULES] || [];
|
||||
const sync = useSync();
|
||||
const { data: list, save } = useStorage(STOKEY_RULES, DEFAULT_RULES);
|
||||
const {
|
||||
sync: { rulesUpdateAt },
|
||||
updateSync,
|
||||
} = useSync();
|
||||
|
||||
const update = async (rules) => {
|
||||
const updateAt = sync.opt?.rulesUpdateAt ? Date.now() : 0;
|
||||
await storage.setObj(STOKEY_RULES, rules);
|
||||
await sync.update({ rulesUpdateAt: updateAt });
|
||||
const updateRules = useCallback(
|
||||
async (rules) => {
|
||||
const updateAt = rulesUpdateAt ? Date.now() : 0;
|
||||
await save(rules);
|
||||
await updateSync({ rulesUpdateAt: updateAt });
|
||||
trySyncRules();
|
||||
};
|
||||
},
|
||||
[rulesUpdateAt]
|
||||
);
|
||||
|
||||
const add = async (rule) => {
|
||||
const add = useCallback(
|
||||
async (rule) => {
|
||||
const rules = [...list];
|
||||
if (rule.pattern === "*") {
|
||||
return;
|
||||
@@ -31,77 +36,90 @@ export function useRules() {
|
||||
return;
|
||||
}
|
||||
rules.unshift(rule);
|
||||
await update(rules);
|
||||
};
|
||||
await updateRules(rules);
|
||||
},
|
||||
[list, updateRules]
|
||||
);
|
||||
|
||||
const del = async (pattern) => {
|
||||
const del = useCallback(
|
||||
async (pattern) => {
|
||||
let rules = [...list];
|
||||
if (pattern === "*") {
|
||||
return;
|
||||
}
|
||||
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];
|
||||
if (pattern === "*") {
|
||||
obj.pattern = "*";
|
||||
}
|
||||
const rule = rules.find((r) => r.pattern === pattern);
|
||||
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];
|
||||
newRules = checkRules(newRules);
|
||||
newRules.forEach((newRule) => {
|
||||
const rule = rules.find((oldRule) => oldRule.pattern === newRule.pattern);
|
||||
const rule = rules.find(
|
||||
(oldRule) => oldRule.pattern === newRule.pattern
|
||||
);
|
||||
if (rule) {
|
||||
Object.assign(rule, newRule);
|
||||
} else {
|
||||
rules.unshift(newRule);
|
||||
}
|
||||
});
|
||||
await update(rules);
|
||||
};
|
||||
await updateRules(rules);
|
||||
},
|
||||
[list, updateRules]
|
||||
);
|
||||
|
||||
return { list, add, del, put, merge };
|
||||
}
|
||||
|
||||
/**
|
||||
* 订阅规则
|
||||
* @returns
|
||||
*/
|
||||
export function useSubrules() {
|
||||
const setting = useSetting();
|
||||
const updateSetting = useSettingUpdate();
|
||||
const list = setting?.subrulesList || DEFAULT_SUBRULES_LIST;
|
||||
// /**
|
||||
// * 订阅规则
|
||||
// * @returns
|
||||
// */
|
||||
// export function useSubrules() {
|
||||
// const setting = useSetting();
|
||||
// const updateSetting = useSettingUpdate();
|
||||
// const list = setting?.subrulesList || DEFAULT_SUBRULES_LIST;
|
||||
|
||||
const select = async (url) => {
|
||||
const subrulesList = [...list];
|
||||
subrulesList.forEach((item) => {
|
||||
if (item.url === url) {
|
||||
item.selected = true;
|
||||
} else {
|
||||
item.selected = false;
|
||||
}
|
||||
});
|
||||
await updateSetting({ subrulesList });
|
||||
};
|
||||
// const select = async (url) => {
|
||||
// const subrulesList = [...list];
|
||||
// subrulesList.forEach((item) => {
|
||||
// if (item.url === url) {
|
||||
// item.selected = true;
|
||||
// } else {
|
||||
// item.selected = false;
|
||||
// }
|
||||
// });
|
||||
// await updateSetting({ subrulesList });
|
||||
// };
|
||||
|
||||
const add = async (url) => {
|
||||
const subrulesList = [...list];
|
||||
subrulesList.push({ url });
|
||||
await updateSetting({ subrulesList });
|
||||
};
|
||||
// const add = async (url) => {
|
||||
// const subrulesList = [...list];
|
||||
// subrulesList.push({ url });
|
||||
// await updateSetting({ subrulesList });
|
||||
// };
|
||||
|
||||
const del = async (url) => {
|
||||
let subrulesList = [...list];
|
||||
subrulesList = subrulesList.filter((item) => item.url !== url);
|
||||
await updateSetting({ subrulesList });
|
||||
};
|
||||
// const del = async (url) => {
|
||||
// let subrulesList = [...list];
|
||||
// subrulesList = subrulesList.filter((item) => item.url !== url);
|
||||
// await updateSetting({ subrulesList });
|
||||
// };
|
||||
|
||||
return { list, select, add, del };
|
||||
}
|
||||
// return { list, select, add, del };
|
||||
// }
|
||||
|
||||
@@ -1,28 +1,79 @@
|
||||
import { STOKEY_SETTING } from "../config";
|
||||
import storage from "../libs/storage";
|
||||
import { useStorages } from "./Storage";
|
||||
import { STOKEY_SETTING, DEFAULT_SETTING } from "../config";
|
||||
import { useStorage } from "./Storage";
|
||||
import { useSync } from "./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
|
||||
* @returns
|
||||
*/
|
||||
export function useSetting() {
|
||||
const storages = useStorages();
|
||||
return storages?.[STOKEY_SETTING];
|
||||
return useContext(SettingContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新设置
|
||||
* @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();
|
||||
};
|
||||
}
|
||||
// export function useSetting() {
|
||||
// const [setting,setSeting]= useState(null);
|
||||
// useEffect(()=>{
|
||||
// (async ()=>{
|
||||
// const
|
||||
// })()
|
||||
// },[])
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * 设置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();
|
||||
// };
|
||||
// }
|
||||
|
||||
@@ -1,91 +1,115 @@
|
||||
import { createContext, useContext, useEffect, useState } from "react";
|
||||
import { browser, isExt, isGm, isWeb } from "../libs/browser";
|
||||
import {
|
||||
STOKEY_SETTING,
|
||||
STOKEY_RULES,
|
||||
STOKEY_SYNC,
|
||||
DEFAULT_SETTING,
|
||||
DEFAULT_RULES,
|
||||
DEFAULT_SYNC,
|
||||
} from "../config";
|
||||
import storage from "../libs/storage";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { storage } from "../libs/storage";
|
||||
|
||||
/**
|
||||
* 默认配置
|
||||
*/
|
||||
export const defaultStorage = {
|
||||
[STOKEY_SETTING]: DEFAULT_SETTING,
|
||||
[STOKEY_RULES]: DEFAULT_RULES,
|
||||
[STOKEY_SYNC]: DEFAULT_SYNC,
|
||||
};
|
||||
export function useStorage(key, defaultVal = null) {
|
||||
const [data, setData] = useState(defaultVal);
|
||||
|
||||
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 save = useCallback(
|
||||
async (val) => {
|
||||
setData(val);
|
||||
await storage.setObj(key, val);
|
||||
},
|
||||
};
|
||||
}
|
||||
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 }));
|
||||
}
|
||||
};
|
||||
[key]
|
||||
);
|
||||
|
||||
const update = useCallback(
|
||||
async (obj) => {
|
||||
setData((pre) => ({ ...pre, ...obj }));
|
||||
await storage.putObj(key, obj);
|
||||
},
|
||||
[key]
|
||||
);
|
||||
|
||||
const remove = useCallback(async () => {
|
||||
setData(null);
|
||||
await storage.del(key);
|
||||
}, [key]);
|
||||
|
||||
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);
|
||||
setData(await storage.getObj(key));
|
||||
})();
|
||||
}, [key]);
|
||||
|
||||
// 监听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>
|
||||
);
|
||||
return { data, save, update, remove };
|
||||
}
|
||||
|
||||
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
47
src/hooks/SubRules.js
Normal 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 };
|
||||
}
|
||||
@@ -1,20 +1,22 @@
|
||||
import { useCallback } from "react";
|
||||
import { STOKEY_SYNC } from "../config";
|
||||
import storage from "../libs/storage";
|
||||
import { useStorages } from "./Storage";
|
||||
import { STOKEY_SYNC, DEFAULT_SYNC } from "../config";
|
||||
import { useStorage } from "./Storage";
|
||||
|
||||
/**
|
||||
* sync hook
|
||||
* @returns
|
||||
*/
|
||||
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,
|
||||
};
|
||||
const { data, update } = useStorage(STOKEY_SYNC, DEFAULT_SYNC);
|
||||
return { sync: data, updateSync: 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,
|
||||
// };
|
||||
// }
|
||||
|
||||
@@ -9,8 +9,8 @@ import { THEME_DARK, THEME_LIGHT } from "../config";
|
||||
* @param {*} param0
|
||||
* @returns
|
||||
*/
|
||||
export default function MuiThemeProvider({ children, options }) {
|
||||
const darkMode = useDarkMode();
|
||||
export default function Theme({ children, options }) {
|
||||
const { darkMode } = useDarkMode();
|
||||
const theme = useMemo(() => {
|
||||
return createTheme({
|
||||
palette: {
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { useEffect } from "react";
|
||||
import { useState } from "react";
|
||||
import { detectLang } from "../libs";
|
||||
import { detectLang } from "../libs/browser";
|
||||
import { apiTranslate } from "../apis";
|
||||
|
||||
/**
|
||||
* 翻译hook
|
||||
* @param {*} q
|
||||
* @param {*} rule
|
||||
* @param {*} setting
|
||||
* @returns
|
||||
*/
|
||||
export function useTranslate(q, rule) {
|
||||
export function useTranslate(q, rule, setting) {
|
||||
const [text, setText] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [sameLang, setSamelang] = useState(false);
|
||||
@@ -30,6 +31,7 @@ export function useTranslate(q, rule) {
|
||||
q,
|
||||
fromLang,
|
||||
toLang,
|
||||
setting,
|
||||
});
|
||||
setText(trText);
|
||||
setSamelang(isSame);
|
||||
@@ -40,7 +42,7 @@ export function useTranslate(q, rule) {
|
||||
setLoading(false);
|
||||
}
|
||||
})();
|
||||
}, [q, translator, fromLang, toLang]);
|
||||
}, [q, translator, fromLang, toLang, setting]);
|
||||
|
||||
return { text, sameLang, loading };
|
||||
}
|
||||
|
||||
@@ -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 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;
|
||||
// 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;
|
||||
|
||||
/**
|
||||
* 本地语言识别
|
||||
* @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
6
src/libs/client.js
Normal 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;
|
||||
@@ -1,4 +1,4 @@
|
||||
import { isExt, isGm } from "./browser";
|
||||
import { isExt, isGm } from "./client";
|
||||
import { sendMsg } from "./msg";
|
||||
import { taskPool } from "./pool";
|
||||
import {
|
||||
|
||||
@@ -1,108 +1,12 @@
|
||||
import storage from "./storage";
|
||||
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";
|
||||
import { CACHE_NAME } from "../config";
|
||||
|
||||
/**
|
||||
* 查询storage中的设置
|
||||
* @returns
|
||||
* 清除缓存数据
|
||||
*/
|
||||
export const getSetting = 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) {
|
||||
export const tryClearCaches = async () => {
|
||||
try {
|
||||
const selectedSub = subrulesList.find((item) => item.selected);
|
||||
if (selectedSub?.url) {
|
||||
const subRules = await loadSubRules(selectedSub.url);
|
||||
rules.splice(-1, 0, ...subRules);
|
||||
}
|
||||
caches.delete(CACHE_NAME);
|
||||
} 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;
|
||||
};
|
||||
|
||||
/**
|
||||
* 本地语言识别
|
||||
* @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);
|
||||
console.log("[clean caches]", err.message);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,18 +1,78 @@
|
||||
import storage from "./storage";
|
||||
import { fetchPolyfill } from "./fetch";
|
||||
import { matchValue, type } from "./utils";
|
||||
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,
|
||||
OPT_TRANS_ALL,
|
||||
OPT_STYLE_ALL,
|
||||
OPT_LANGS_FROM,
|
||||
OPT_LANGS_TO,
|
||||
GLOBLA_RULE,
|
||||
DEFAULT_SUBRULES_LIST,
|
||||
} from "../config";
|
||||
import { syncOpt } from "./sync";
|
||||
|
||||
const fromLangs = OPT_LANGS_FROM.map((item) => item[0]);
|
||||
const toLangs = OPT_LANGS_TO.map((item) => item[0]);
|
||||
// import { syncOpt } from "./sync";
|
||||
|
||||
/**
|
||||
* 根据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
|
||||
@@ -27,6 +87,8 @@ export const checkRules = (rules) => {
|
||||
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();
|
||||
rules = rules
|
||||
.filter((rule) => type(rule) === "object")
|
||||
@@ -62,84 +124,18 @@ export const checkRules = (rules) => {
|
||||
return rules;
|
||||
};
|
||||
|
||||
/**
|
||||
* 订阅规则的本地缓存
|
||||
*/
|
||||
export const rulesCache = {
|
||||
fetch: async (url, isBg = false) => {
|
||||
const res = await fetchPolyfill(url, { isBg });
|
||||
const rules = checkRules(res).filter(
|
||||
(rule) => rule.pattern.replaceAll(GLOBAL_KEY, "") !== ""
|
||||
);
|
||||
return rules;
|
||||
},
|
||||
set: async (url, rules) => {
|
||||
await storage.setObj(`${STOKEY_RULESCACHE_PREFIX}${url}`, rules);
|
||||
},
|
||||
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);
|
||||
};
|
||||
// /**
|
||||
// * 订阅规则的本地缓存
|
||||
// */
|
||||
// export const rulesCache = {
|
||||
// fetch: async (url, isBg = false) => {
|
||||
// const res = await fetchPolyfill(url, { isBg });
|
||||
// const rules = checkRules(res).filter(
|
||||
// (rule) => rule.pattern.replaceAll(GLOBAL_KEY, "") !== ""
|
||||
// );
|
||||
// return rules;
|
||||
// },
|
||||
// set: (url, rules) => setSubRules(url, rules),
|
||||
// get: (url) => getSubRulesWithDefault(url),
|
||||
// del: (url) => delSubRules(url),
|
||||
// };
|
||||
|
||||
@@ -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) {
|
||||
if (isExt) {
|
||||
await browser.storage.local.set({ [key]: val });
|
||||
} 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);
|
||||
window.dispatchEvent(
|
||||
new StorageEvent("storage", {
|
||||
key,
|
||||
oldValue,
|
||||
newValue: val,
|
||||
})
|
||||
);
|
||||
// window.dispatchEvent(
|
||||
// new StorageEvent("storage", {
|
||||
// key,
|
||||
// oldValue,
|
||||
// newValue: val,
|
||||
// })
|
||||
// );
|
||||
} else {
|
||||
const oldValue = window.localStorage.getItem(key);
|
||||
// const oldValue = window.localStorage.getItem(key);
|
||||
window.localStorage.setItem(key, val);
|
||||
window.dispatchEvent(
|
||||
new StorageEvent("storage", {
|
||||
key,
|
||||
oldValue,
|
||||
newValue: val,
|
||||
})
|
||||
);
|
||||
// window.dispatchEvent(
|
||||
// new StorageEvent("storage", {
|
||||
// key,
|
||||
// oldValue,
|
||||
// newValue: val,
|
||||
// })
|
||||
// );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,25 +54,25 @@ async function del(key) {
|
||||
if (isExt) {
|
||||
await browser.storage.local.remove([key]);
|
||||
} 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);
|
||||
window.dispatchEvent(
|
||||
new StorageEvent("storage", {
|
||||
key,
|
||||
oldValue,
|
||||
newValue: null,
|
||||
})
|
||||
);
|
||||
// window.dispatchEvent(
|
||||
// new StorageEvent("storage", {
|
||||
// key,
|
||||
// oldValue,
|
||||
// newValue: null,
|
||||
// })
|
||||
// );
|
||||
} else {
|
||||
const oldValue = window.localStorage.getItem(key);
|
||||
// const oldValue = window.localStorage.getItem(key);
|
||||
window.localStorage.removeItem(key);
|
||||
window.dispatchEvent(
|
||||
new StorageEvent("storage", {
|
||||
key,
|
||||
oldValue,
|
||||
newValue: null,
|
||||
})
|
||||
);
|
||||
// window.dispatchEvent(
|
||||
// new StorageEvent("storage", {
|
||||
// key,
|
||||
// oldValue,
|
||||
// newValue: null,
|
||||
// })
|
||||
// );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,22 +96,22 @@ async function putObj(key, obj) {
|
||||
await setObj(key, { ...cur, ...obj });
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听storage事件
|
||||
* @param {*} handleChanged
|
||||
*/
|
||||
function onChanged(handleChanged) {
|
||||
if (isExt) {
|
||||
browser.storage.onChanged.addListener(handleChanged);
|
||||
} else {
|
||||
window.addEventListener("storage", handleChanged);
|
||||
}
|
||||
}
|
||||
// /**
|
||||
// * 监听storage事件
|
||||
// * @param {*} handleChanged
|
||||
// */
|
||||
// function onChanged(handleChanged) {
|
||||
// if (isExt) {
|
||||
// browser.storage.onChanged.addListener(handleChanged);
|
||||
// } else {
|
||||
// window.addEventListener("storage", handleChanged);
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* 对storage的封装
|
||||
*/
|
||||
const storage = {
|
||||
export const storage = {
|
||||
get,
|
||||
set,
|
||||
del,
|
||||
@@ -106,7 +119,70 @@ const storage = {
|
||||
trySetObj,
|
||||
getObj,
|
||||
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
62
src/libs/subRules.js
Normal 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);
|
||||
};
|
||||
@@ -8,27 +8,27 @@ import {
|
||||
STOKEY_RULES,
|
||||
KV_SALT_SHARE,
|
||||
} from "../config";
|
||||
import storage from "../libs/storage";
|
||||
import { storage, getSyncWithDefault, updateSync } from "../libs/storage";
|
||||
import { getSetting, getRules } from ".";
|
||||
import { apiSyncData } from "../apis";
|
||||
import { sha256 } from "./utils";
|
||||
|
||||
/**
|
||||
* 同步相关数据
|
||||
*/
|
||||
export const syncOpt = {
|
||||
load: async () => (await storage.getObj(STOKEY_SYNC)) || DEFAULT_SYNC,
|
||||
update: async (obj) => {
|
||||
await storage.putObj(STOKEY_SYNC, obj);
|
||||
},
|
||||
};
|
||||
// /**
|
||||
// * 同步相关数据
|
||||
// */
|
||||
// export const syncOpt = {
|
||||
// load: async () => (await storage.getObj(STOKEY_SYNC)) || DEFAULT_SYNC,
|
||||
// update: async (obj) => {
|
||||
// await storage.putObj(STOKEY_SYNC, obj);
|
||||
// },
|
||||
// };
|
||||
|
||||
/**
|
||||
* 同步设置
|
||||
* @returns
|
||||
*/
|
||||
export const syncSetting = async (isBg = false) => {
|
||||
const { syncUrl, syncKey, settingUpdateAt } = await syncOpt.load();
|
||||
const syncSetting = async (isBg = false) => {
|
||||
const { syncUrl, syncKey, settingUpdateAt } = await getSyncWithDefault();
|
||||
if (!syncUrl || !syncKey) {
|
||||
return;
|
||||
}
|
||||
@@ -46,13 +46,13 @@ export const syncSetting = async (isBg = false) => {
|
||||
);
|
||||
|
||||
if (res && res.updateAt > settingUpdateAt) {
|
||||
await syncOpt.update({
|
||||
await updateSync({
|
||||
settingUpdateAt: res.updateAt,
|
||||
settingSyncAt: res.updateAt,
|
||||
});
|
||||
await storage.setObj(STOKEY_SETTING, res.value);
|
||||
} else {
|
||||
await syncOpt.update({ settingSyncAt: res.updateAt });
|
||||
await updateSync({ settingSyncAt: res.updateAt });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -68,8 +68,8 @@ export const trySyncSetting = async (isBg = false) => {
|
||||
* 同步规则
|
||||
* @returns
|
||||
*/
|
||||
export const syncRules = async (isBg = false) => {
|
||||
const { syncUrl, syncKey, rulesUpdateAt } = await syncOpt.load();
|
||||
const syncRules = async (isBg = false) => {
|
||||
const { syncUrl, syncKey, rulesUpdateAt } = await getSyncWithDefault();
|
||||
if (!syncUrl || !syncKey) {
|
||||
return;
|
||||
}
|
||||
@@ -87,13 +87,13 @@ export const syncRules = async (isBg = false) => {
|
||||
);
|
||||
|
||||
if (res && res.updateAt > rulesUpdateAt) {
|
||||
await syncOpt.update({
|
||||
await updateSync({
|
||||
rulesUpdateAt: res.updateAt,
|
||||
rulesSyncAt: res.updateAt,
|
||||
});
|
||||
await storage.setObj(STOKEY_RULES, res.value);
|
||||
} else {
|
||||
await syncOpt.update({ rulesSyncAt: res.updateAt });
|
||||
await updateSync({ rulesSyncAt: res.updateAt });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -12,14 +12,16 @@ import {
|
||||
import Content from "../views/Content";
|
||||
import { fetchUpdate, fetchClear } from "./fetch";
|
||||
import { debounce } from "./utils";
|
||||
import { isExt } from "./client";
|
||||
|
||||
/**
|
||||
* 翻译类
|
||||
*/
|
||||
export class Translator {
|
||||
_rule = {};
|
||||
_minLength = 0;
|
||||
_maxLength = 0;
|
||||
_setting = {};
|
||||
_rootNodes = new Set();
|
||||
_tranNodes = new Map();
|
||||
_skipNodeNames = [
|
||||
APP_LCNAME,
|
||||
"style",
|
||||
@@ -36,8 +38,6 @@ export class Translator {
|
||||
"script",
|
||||
"iframe",
|
||||
];
|
||||
_rootNodes = new Set();
|
||||
_tranNodes = new Map();
|
||||
|
||||
// 显示
|
||||
_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);
|
||||
this._overrideAttachShadow();
|
||||
this._minLength = minLength ?? TRANS_MIN_LENGTH;
|
||||
this._maxLength = maxLength ?? TRANS_MAX_LENGTH;
|
||||
this.rule = rule;
|
||||
|
||||
this._setting = setting;
|
||||
this._rule = rule;
|
||||
|
||||
if (rule.transOpen === "true") {
|
||||
this._register();
|
||||
}
|
||||
@@ -268,7 +270,11 @@ export class Translator {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { StoragesProvider } from "./hooks/Storage";
|
||||
import { SettingProvider } from "./hooks/Setting";
|
||||
import ThemeProvider from "./hooks/Theme";
|
||||
import Popup from "./views/Popup";
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById("root"));
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<StoragesProvider>
|
||||
<SettingProvider>
|
||||
<ThemeProvider>
|
||||
<Popup />
|
||||
</ThemeProvider>
|
||||
</StoragesProvider>
|
||||
</SettingProvider>
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
||||
@@ -5,8 +5,8 @@ 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";
|
||||
import { isGm } from "./libs/browser";
|
||||
import { trySyncAllSubRules } from "./libs/subRules";
|
||||
import { isGm } from "./libs/client";
|
||||
import { MSG_TRANS_TOGGLE, MSG_TRANS_PUTRULE } from "./config";
|
||||
import { isIframe } from "./libs/iframe";
|
||||
import { handlePing, injectScript } from "./libs/gm";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { limitNumber } from "../../libs/utils";
|
||||
import { isMobile } from "../../libs/mobile";
|
||||
import { setFab } from "../../libs";
|
||||
import { setFab } from "../../libs/storage";
|
||||
|
||||
const getEdgePosition = (
|
||||
{ x: left, y: top, edge },
|
||||
|
||||
@@ -8,7 +8,7 @@ import IconButton from "@mui/material/IconButton";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import Stack from "@mui/material/Stack";
|
||||
import { useEffect, useState, useMemo, useCallback } from "react";
|
||||
import { StoragesProvider } from "../../hooks/Storage";
|
||||
import { SettingProvider } from "../../hooks/Setting";
|
||||
import Popup from "../Popup";
|
||||
import { debounce } from "../../libs/utils";
|
||||
|
||||
@@ -81,7 +81,7 @@ export default function Action({ translator, fab }) {
|
||||
};
|
||||
|
||||
return (
|
||||
<StoragesProvider>
|
||||
<SettingProvider>
|
||||
<ThemeProvider>
|
||||
<Draggable
|
||||
key="pop"
|
||||
@@ -139,6 +139,6 @@ export default function Action({ translator, fab }) {
|
||||
}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
</StoragesProvider>
|
||||
</SettingProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import { useTranslate } from "../../hooks/Translate";
|
||||
export default function Content({ q, translator }) {
|
||||
const [rule, setRule] = useState(translator.rule);
|
||||
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 handleMouseEnter = () => {
|
||||
|
||||
@@ -4,7 +4,6 @@ import IconButton from "@mui/material/IconButton";
|
||||
import MenuIcon from "@mui/icons-material/Menu";
|
||||
import Toolbar from "@mui/material/Toolbar";
|
||||
import Box from "@mui/material/Box";
|
||||
import { useDarkModeSwitch } from "../../hooks/ColorMode";
|
||||
import { useDarkMode } from "../../hooks/ColorMode";
|
||||
import LightModeIcon from "@mui/icons-material/LightMode";
|
||||
import DarkModeIcon from "@mui/icons-material/DarkMode";
|
||||
@@ -14,8 +13,7 @@ import { useI18n } from "../../hooks/I18n";
|
||||
function Header(props) {
|
||||
const i18n = useI18n();
|
||||
const { onDrawerToggle } = props;
|
||||
const switchColorMode = useDarkModeSwitch();
|
||||
const darkMode = useDarkMode();
|
||||
const { darkMode, toggleDarkMode } = useDarkMode();
|
||||
|
||||
return (
|
||||
<AppBar
|
||||
@@ -43,7 +41,7 @@ function Header(props) {
|
||||
href={process.env.REACT_APP_HOMEPAGE}
|
||||
>{`${i18n("app_name")} v${process.env.REACT_APP_VERSION}`}</Link>
|
||||
</Box>
|
||||
<IconButton onClick={switchColorMode} color="inherit">
|
||||
<IconButton onClick={toggleDarkMode} color="inherit">
|
||||
{darkMode ? <LightModeIcon /> : <DarkModeIcon />}
|
||||
</IconButton>
|
||||
</Toolbar>
|
||||
|
||||
@@ -5,7 +5,7 @@ import TextField from "@mui/material/TextField";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import FormControl from "@mui/material/FormControl";
|
||||
import Select from "@mui/material/Select";
|
||||
import { useSetting, useSettingUpdate } from "../../hooks/Setting";
|
||||
import { useSetting } from "../../hooks/Setting";
|
||||
import { limitNumber, debounce } from "../../libs/utils";
|
||||
import { useI18n } from "../../hooks/I18n";
|
||||
import { UI_LANGS } from "../../config";
|
||||
@@ -13,8 +13,7 @@ import { useMemo } from "react";
|
||||
|
||||
export default function Settings() {
|
||||
const i18n = useI18n();
|
||||
const setting = useSetting();
|
||||
const updateSetting = useSettingUpdate();
|
||||
const { setting, updateSetting } = useSetting();
|
||||
|
||||
const handleChange = useMemo(
|
||||
() =>
|
||||
|
||||
@@ -4,10 +4,10 @@ import Rules from "./Rules";
|
||||
import Setting from "./Setting";
|
||||
import Layout from "./Layout";
|
||||
import SyncSetting from "./SyncSetting";
|
||||
import { StoragesProvider } from "../../hooks/Storage";
|
||||
import { SettingProvider } from "../../hooks/Setting";
|
||||
import ThemeProvider from "../../hooks/Theme";
|
||||
import { useEffect, useState } from "react";
|
||||
import { isGm } from "../../libs/browser";
|
||||
import { isGm } from "../../libs/client";
|
||||
import { sleep } from "../../libs/utils";
|
||||
import CircularProgress from "@mui/material/CircularProgress";
|
||||
import { trySyncAll } from "../../libs/sync";
|
||||
@@ -89,7 +89,7 @@ export default function Options() {
|
||||
}
|
||||
|
||||
return (
|
||||
<StoragesProvider>
|
||||
<SettingProvider>
|
||||
<ThemeProvider>
|
||||
<AlertProvider>
|
||||
<HashRouter>
|
||||
@@ -104,6 +104,6 @@ export default function Options() {
|
||||
</HashRouter>
|
||||
</AlertProvider>
|
||||
</ThemeProvider>
|
||||
</StoragesProvider>
|
||||
</SettingProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,8 @@ import FormControlLabel from "@mui/material/FormControlLabel";
|
||||
import Switch from "@mui/material/Switch";
|
||||
import Button from "@mui/material/Button";
|
||||
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 TextField from "@mui/material/TextField";
|
||||
import {
|
||||
|
||||
Reference in New Issue
Block a user