dev......

This commit is contained in:
Gabe Yuan
2023-08-31 00:18:57 +08:00
parent c46fe7d1c6
commit aa795e2731
21 changed files with 228 additions and 449 deletions

View File

@@ -1,7 +1,6 @@
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,
@@ -11,9 +10,8 @@ import {
PROMPT_PLACE_TO, PROMPT_PLACE_TO,
KV_SALT_SYNC, KV_SALT_SYNC,
} from "../config"; } from "../config";
import { detectLang } from "../libs/browser"; import { detectLang } from "../libs";
import { sha256 } from "../libs/utils"; import { sha256 } from "../libs/utils";
import { checkRules } from "../libs/rules";
/** /**
* 同步数据 * 同步数据
@@ -39,13 +37,8 @@ export const apiSyncData = async (url, key, data, isBg = false) =>
* @param {*} isBg * @param {*} isBg
* @returns * @returns
*/ */
export const apiFetchRules = async (url, isBg = false) => { export const apiFetchRules = (url, isBg = false) =>
const res = await fetchPolyfill(url, { isBg }); fetchPolyfill(url, { isBg });
const rules = checkRules(res).filter(
(rule) => rule.pattern.replaceAll(GLOBAL_KEY, "") !== ""
);
return rules;
};
/** /**
* 谷歌翻译 * 谷歌翻译

View File

@@ -159,7 +159,7 @@ export const DEFAULT_SUBRULES_LIST = [
selected: true, selected: true,
}, },
{ {
url: "https://fishjar.github.io/kiss-translator/kiss-translator-rules.json", url: process.env.REACT_APP_RULESURL2,
selected: false, selected: false,
}, },
]; ];

View File

@@ -13,7 +13,7 @@ export function useDarkMode() {
const toggleDarkMode = useCallback(async () => { const toggleDarkMode = useCallback(async () => {
await updateSetting({ darkMode: !darkMode }); await updateSetting({ darkMode: !darkMode });
}, [darkMode]); }, [darkMode, updateSetting]);
return { darkMode, toggleDarkMode }; return { darkMode, toggleDarkMode };
} }

View File

@@ -23,7 +23,7 @@ export function useRules() {
await updateSync({ rulesUpdateAt: updateAt }); await updateSync({ rulesUpdateAt: updateAt });
trySyncRules(); trySyncRules();
}, },
[rulesUpdateAt] [rulesUpdateAt, save, updateSync]
); );
const add = useCallback( const add = useCallback(
@@ -87,39 +87,3 @@ export function useRules() {
return { list, add, del, put, merge }; return { list, add, del, put, merge };
} }
// /**
// * 订阅规则
// * @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 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 });
// };
// return { list, select, add, del };
// }

View File

@@ -23,7 +23,7 @@ export function SettingProvider({ children }) {
await updateSync({ settingUpdateAt: updateAt }); await updateSync({ settingUpdateAt: updateAt });
trySyncSetting(); trySyncSetting();
}, },
[settingUpdateAt] [settingUpdateAt, update, updateSync]
); );
return ( return (
@@ -45,35 +45,3 @@ export function SettingProvider({ children }) {
export function useSetting() { export function useSetting() {
return useContext(SettingContext); return useContext(SettingContext);
} }
// 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();
// };
// }

View File

@@ -33,83 +33,3 @@ export function useStorage(key, defaultVal = null) {
return { data, save, update, remove }; return { data, save, update, remove };
} }
// /**
// * 默认配置
// */
// 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);
// }

View File

@@ -1,47 +1,81 @@
import { DEFAULT_SUBRULES_LIST } from "../config"; import { DEFAULT_SUBRULES_LIST } from "../config";
import { useSetting } from "./Setting"; import { useSetting } from "./Setting";
import { useCallback } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import { loadOrFetchSubRules } from "../libs/subRules";
import { delSubRules } from "../libs/storage";
/** /**
* 订阅规则 * 订阅规则
* @returns * @returns
*/ */
export function useSubRules() { export function useSubRules() {
const { data: setting, update: updateSetting } = useSetting(); const [loading, setLoading] = useState(false);
const list = setting?.subRulesList || DEFAULT_SUBRULES_LIST; const [selectedRules, setSelectedRules] = useState([]);
const { setting, updateSetting } = useSetting();
const list = setting?.subrulesList || DEFAULT_SUBRULES_LIST;
const select = useCallback( const selectedSub = useMemo(() => list.find((item) => item.selected), [list]);
const selectedUrl = selectedSub.url;
const selectSub = useCallback(
async (url) => { 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 });
}, },
[list] [list, updateSetting]
); );
const add = useCallback( const addSub = useCallback(
async (url) => { async (url) => {
const subRulesList = [...list]; const subrulesList = [...list];
subRulesList.push({ url, selected: false }); subrulesList.push({ url, selected: false });
await updateSetting({ subRulesList }); await updateSetting({ subrulesList });
}, },
[list] [list, updateSetting]
); );
const del = useCallback( const delSub = useCallback(
async (url) => { 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 });
await delSubRules(url);
}, },
[list] [list, updateSetting]
); );
return { list, select, add, del }; useEffect(() => {
(async () => {
if (selectedUrl) {
try {
setLoading(true);
const rules = await loadOrFetchSubRules(selectedUrl);
setSelectedRules(rules);
} catch (err) {
console.log("[loadOrFetchSubRules]", err);
} finally {
setLoading(false);
}
}
})();
}, [selectedUrl]);
return {
subList: list,
selectSub,
addSub,
delSub,
selectedSub,
selectedUrl,
selectedRules,
setSelectedRules,
loading,
};
} }

View File

@@ -9,14 +9,3 @@ export function useSync() {
const { data, update } = useStorage(STOKEY_SYNC, DEFAULT_SYNC); const { data, update } = useStorage(STOKEY_SYNC, DEFAULT_SYNC);
return { sync: data, updateSync: update }; 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,
// };
// }

View File

@@ -1,6 +1,6 @@
import { useEffect } from "react"; import { useEffect } from "react";
import { useState } from "react"; import { useState } from "react";
import { detectLang } from "../libs/browser"; import { detectLang } from "../libs";
import { apiTranslate } from "../apis"; import { apiTranslate } from "../apis";
/** /**

View File

@@ -1,5 +1,5 @@
import storage from "./storage"; import { getMsauth, setMsauth } from "./storage";
import { STOKEY_MSAUTH, URL_MICROSOFT_AUTH } from "../config"; import { URL_MICROSOFT_AUTH } from "../config";
import { fetchData } from "./fetch"; import { fetchData } from "./fetch";
const parseMSToken = (token) => { const parseMSToken = (token) => {
@@ -26,9 +26,9 @@ const _msAuth = () => {
} }
// 查询storage缓存 // 查询storage缓存
const res = (await storage.getObj(STOKEY_MSAUTH)) || {}; const res = await getMsauth();
token = res.token; token = res?.token;
exp = res.exp; exp = res?.exp;
if (token && exp * 1000 > now + 1000) { if (token && exp * 1000 > now + 1000) {
return [token, exp]; return [token, exp];
} }
@@ -36,7 +36,7 @@ const _msAuth = () => {
// 缓存没有或失效,查询接口 // 缓存没有或失效,查询接口
token = await fetchData(URL_MICROSOFT_AUTH); token = await fetchData(URL_MICROSOFT_AUTH);
exp = parseMSToken(token); exp = parseMSToken(token);
await storage.setObj(STOKEY_MSAUTH, { token, exp }); await setMsauth({ token, exp });
return [token, exp]; return [token, exp];
}; };
}; };

View File

@@ -13,21 +13,3 @@ function _browser() {
} }
export const browser = _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;
/**
* 本地语言识别
* @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,4 +1,5 @@
import { CACHE_NAME } from "../config"; import { CACHE_NAME } from "../config";
import { browser } from "./browser";
/** /**
* 清除缓存数据 * 清除缓存数据
@@ -10,3 +11,17 @@ export const tryClearCaches = async () => {
console.log("[clean caches]", err.message); console.log("[clean caches]", err.message);
} }
}; };
/**
* 本地语言识别
* @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,12 +1,3 @@
import {
getSyncWithDefault,
updateSync,
getSubRulesWithDefault,
getSubRules,
delSubRules,
setSubRules,
} from "./storage";
import { fetchPolyfill } from "./fetch";
import { matchValue, type, isMatch } from "./utils"; import { matchValue, type, isMatch } from "./utils";
import { import {
GLOBAL_KEY, GLOBAL_KEY,
@@ -17,8 +8,7 @@ import {
GLOBLA_RULE, GLOBLA_RULE,
DEFAULT_SUBRULES_LIST, DEFAULT_SUBRULES_LIST,
} from "../config"; } from "../config";
import { loadOrFetchSubRules } from "./subRules";
// import { syncOpt } from "./sync";
/** /**
* 根据href匹配规则 * 根据href匹配规则
@@ -36,7 +26,7 @@ export const matchRule = async (
try { try {
const selectedSub = subrulesList.find((item) => item.selected); const selectedSub = subrulesList.find((item) => item.selected);
if (selectedSub?.url) { if (selectedSub?.url) {
const subRules = await loadSubRules(selectedSub.url); const subRules = await loadOrFetchSubRules(selectedSub.url);
rules.splice(-1, 0, ...subRules); rules.splice(-1, 0, ...subRules);
} }
} catch (err) { } catch (err) {
@@ -123,19 +113,3 @@ export const checkRules = (rules) => {
return 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: (url, rules) => setSubRules(url, rules),
// get: (url) => getSubRulesWithDefault(url),
// del: (url) => delSubRules(url),
// };

View File

@@ -10,32 +10,16 @@ import {
DEFAULT_SYNC, DEFAULT_SYNC,
BUILTIN_RULES, BUILTIN_RULES,
} from "../config"; } from "../config";
import { browser, isExt, isGm } from "./client"; import { isExt, isGm } from "./client";
// import { APP_NAME } from "../config/app"; import { browser } from "./browser";
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);
await (window.KISS_GM || GM).setValue(key, val); await (window.KISS_GM || GM).setValue(key, val);
// window.dispatchEvent(
// new StorageEvent("storage", {
// key,
// oldValue,
// newValue: val,
// })
// );
} else { } else {
// const oldValue = window.localStorage.getItem(key);
window.localStorage.setItem(key, val); window.localStorage.setItem(key, val);
// window.dispatchEvent(
// new StorageEvent("storage", {
// key,
// oldValue,
// newValue: val,
// })
// );
} }
} }
@@ -54,25 +38,9 @@ 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);
await (window.KISS_GM || GM).deleteValue(key); await (window.KISS_GM || GM).deleteValue(key);
// window.dispatchEvent(
// new StorageEvent("storage", {
// key,
// oldValue,
// newValue: null,
// })
// );
} else { } else {
// const oldValue = window.localStorage.getItem(key);
window.localStorage.removeItem(key); window.localStorage.removeItem(key);
// window.dispatchEvent(
// new StorageEvent("storage", {
// key,
// oldValue,
// newValue: null,
// })
// );
} }
} }
@@ -96,18 +64,6 @@ async function putObj(key, obj) {
await setObj(key, { ...cur, ...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的封装 * 对storage的封装
*/ */

View File

@@ -1,5 +1,12 @@
import { getSyncWithDefault, updateSync } from "./storage"; import { GLOBAL_KEY } from "../config";
import {
getSyncWithDefault,
updateSync,
setSubRules,
getSubRules,
} from "./storage";
import { apiFetchRules } from "../apis"; import { apiFetchRules } from "../apis";
import { checkRules } from "./rules";
/** /**
* 同步订阅规则 * 同步订阅规则
@@ -7,9 +14,12 @@ import { apiFetchRules } from "../apis";
* @returns * @returns
*/ */
export const syncSubRules = async (url, isBg = false) => { export const syncSubRules = async (url, isBg = false) => {
const rules = await apiFetchRules(url, isBg); const res = await apiFetchRules(url, isBg);
const rules = checkRules(res).filter(
(rule) => rule.pattern.replaceAll(GLOBAL_KEY, "") !== ""
);
if (rules.length > 0) { if (rules.length > 0) {
await rulesCache.set(url, rules); await setSubRules(url, rules);
} }
return rules; return rules;
}; };
@@ -54,7 +64,7 @@ export const trySyncAllSubRules = async ({ subrulesList }, isBg = false) => {
* @returns * @returns
*/ */
export const loadOrFetchSubRules = async (url) => { export const loadOrFetchSubRules = async (url) => {
const rules = await apiFetchRules(url); const rules = await getSubRules(url);
if (rules?.length) { if (rules?.length) {
return rules; return rules;
} }

View File

@@ -1,28 +1,20 @@
import { import {
STOKEY_SYNC,
DEFAULT_SYNC,
KV_SETTING_KEY, KV_SETTING_KEY,
KV_RULES_KEY, KV_RULES_KEY,
KV_RULES_SHARE_KEY, KV_RULES_SHARE_KEY,
STOKEY_SETTING,
STOKEY_RULES,
KV_SALT_SHARE, KV_SALT_SHARE,
} from "../config"; } from "../config";
import { storage, getSyncWithDefault, updateSync } from "../libs/storage"; import {
import { getSetting, getRules } from "."; getSyncWithDefault,
updateSync,
getSettingWithDefault,
getRulesWithDefault,
setSetting,
setRules,
} from "./storage";
import { apiSyncData } from "../apis"; import { apiSyncData } from "../apis";
import { sha256 } from "./utils"; 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);
// },
// };
/** /**
* 同步设置 * 同步设置
* @returns * @returns
@@ -33,7 +25,7 @@ const syncSetting = async (isBg = false) => {
return; return;
} }
const setting = await getSetting(); const setting = await getSettingWithDefault();
const res = await apiSyncData( const res = await apiSyncData(
syncUrl, syncUrl,
syncKey, syncKey,
@@ -50,7 +42,7 @@ const syncSetting = async (isBg = false) => {
settingUpdateAt: res.updateAt, settingUpdateAt: res.updateAt,
settingSyncAt: res.updateAt, settingSyncAt: res.updateAt,
}); });
await storage.setObj(STOKEY_SETTING, res.value); await setSetting(res.value);
} else { } else {
await updateSync({ settingSyncAt: res.updateAt }); await updateSync({ settingSyncAt: res.updateAt });
} }
@@ -74,7 +66,7 @@ const syncRules = async (isBg = false) => {
return; return;
} }
const rules = await getRules(); const rules = await getRulesWithDefault();
const res = await apiSyncData( const res = await apiSyncData(
syncUrl, syncUrl,
syncKey, syncKey,
@@ -91,7 +83,7 @@ const syncRules = async (isBg = false) => {
rulesUpdateAt: res.updateAt, rulesUpdateAt: res.updateAt,
rulesSyncAt: res.updateAt, rulesSyncAt: res.updateAt,
}); });
await storage.setObj(STOKEY_RULES, res.value); await setRules(res.value);
} else { } else {
await updateSync({ rulesSyncAt: res.updateAt }); await updateSync({ rulesSyncAt: res.updateAt });
} }

View File

@@ -12,7 +12,6 @@ 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";
/** /**
* 翻译类 * 翻译类
@@ -102,6 +101,10 @@ export class Translator {
} }
} }
get setting() {
return this._setting;
}
get rule() { get rule() {
// console.log("get rule", this._rule); // console.log("get rule", this._rule);
return this._rule; return this._rule;

View File

@@ -3,13 +3,14 @@ import ReactDOM from "react-dom/client";
import Action from "./views/Action"; import Action from "./views/Action";
import createCache from "@emotion/cache"; import createCache from "@emotion/cache";
import { CacheProvider } from "@emotion/react"; import { CacheProvider } from "@emotion/react";
import { getSetting, getRules, matchRule, getFab } from "./libs"; import { getSettingWithDefault, getRulesWithDefault, getFabWithDefault } from "./libs/storage";
import { Translator } from "./libs/translator"; import { Translator } from "./libs/translator";
import { trySyncAllSubRules } from "./libs/subRules"; import { trySyncAllSubRules } from "./libs/subRules";
import { isGm } from "./libs/client"; 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";
import { matchRule } from "./libs/rules";
/** /**
* 入口函数 * 入口函数
@@ -36,8 +37,8 @@ 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);
@@ -59,7 +60,7 @@ const init = async () => {
} }
// 浮球按钮 // 浮球按钮
const fab = await getFab(); const fab = await getFabWithDefault();
const $action = document.createElement("div"); const $action = document.createElement("div");
$action.setAttribute("id", "kiss-translator"); $action.setAttribute("id", "kiss-translator");
document.body.parentElement.appendChild($action); document.body.parentElement.appendChild($action);

View File

@@ -24,7 +24,7 @@ import MenuItem from "@mui/material/MenuItem";
import Grid from "@mui/material/Grid"; import Grid from "@mui/material/Grid";
import FileDownloadIcon from "@mui/icons-material/FileDownload"; import FileDownloadIcon from "@mui/icons-material/FileDownload";
import FileUploadIcon from "@mui/icons-material/FileUpload"; import FileUploadIcon from "@mui/icons-material/FileUpload";
import { useSetting, useSettingUpdate } from "../../hooks/Setting"; import { useSetting } from "../../hooks/Setting";
import FormControlLabel from "@mui/material/FormControlLabel"; import FormControlLabel from "@mui/material/FormControlLabel";
import Switch from "@mui/material/Switch"; import Switch from "@mui/material/Switch";
import Tabs from "@mui/material/Tabs"; import Tabs from "@mui/material/Tabs";
@@ -35,11 +35,13 @@ import DeleteIcon from "@mui/icons-material/Delete";
import IconButton from "@mui/material/IconButton"; import IconButton from "@mui/material/IconButton";
import ShareIcon from "@mui/icons-material/Share"; import ShareIcon from "@mui/icons-material/Share";
import SyncIcon from "@mui/icons-material/Sync"; import SyncIcon from "@mui/icons-material/Sync";
import { useSubrules } from "../../hooks/Rules"; import { useSubRules } from "../../hooks/SubRules";
import { rulesCache, loadSubRules, syncSubRules } from "../../libs/rules"; import { syncSubRules } from "../../libs/subRules";
import { loadOrFetchSubRules } from "../../libs/subRules";
import { useAlert } from "../../hooks/Alert"; import { useAlert } from "../../hooks/Alert";
import { syncOpt, syncShareRules } from "../../libs/sync"; import { syncShareRules } from "../../libs/sync";
import { debounce } from "../../libs/utils"; import { debounce } from "../../libs/utils";
import { delSubRules, getSyncWithDefault } from "../../libs/storage";
function RuleFields({ rule, rules, setShow, setKeyword }) { function RuleFields({ rule, rules, setShow, setKeyword }) {
const initFormValues = rule || { ...DEFAULT_RULE, transOpen: "true" }; const initFormValues = rule || { ...DEFAULT_RULE, transOpen: "true" };
@@ -409,12 +411,12 @@ function UploadButton({ onChange, text }) {
); );
} }
function ShareButton({ rules, injectRules, selectedSub }) { function ShareButton({ rules, injectRules, selectedUrl }) {
const alert = useAlert(); const alert = useAlert();
const i18n = useI18n(); const i18n = useI18n();
const handleClick = async () => { const handleClick = async () => {
try { try {
const { syncUrl, syncKey } = await syncOpt.load(); const { syncUrl, syncKey } = await getSyncWithDefault();
if (!syncUrl || !syncKey) { if (!syncUrl || !syncKey) {
alert.warning(i18n("error_sync_setting")); alert.warning(i18n("error_sync_setting"));
return; return;
@@ -422,7 +424,7 @@ function ShareButton({ rules, injectRules, selectedSub }) {
const shareRules = [...rules.list]; const shareRules = [...rules.list];
if (injectRules) { if (injectRules) {
const subRules = await loadSubRules(selectedSub?.url); const subRules = await loadOrFetchSubRules(selectedUrl);
shareRules.splice(-1, 0, ...subRules); shareRules.splice(-1, 0, ...subRules);
} }
@@ -451,19 +453,15 @@ function ShareButton({ rules, injectRules, selectedSub }) {
); );
} }
function UserRules() { function UserRules({ subRules }) {
const i18n = useI18n(); const i18n = useI18n();
const rules = useRules(); const rules = useRules();
const [showAdd, setShowAdd] = useState(false); const [showAdd, setShowAdd] = useState(false);
const setting = useSetting(); const { setting, updateSetting } = useSetting();
const updateSetting = useSettingUpdate();
const subrules = useSubrules();
const [subRules, setSubRules] = useState([]);
const [keyword, setKeyword] = useState(""); const [keyword, setKeyword] = useState("");
const selectedSub = subrules.list.find((item) => item.selected);
const injectRules = !!setting?.injectRules; const injectRules = !!setting?.injectRules;
const { selectedUrl, selectedRules } = subRules;
const handleImport = (e) => { const handleImport = (e) => {
const file = e.target.files[0]; const file = e.target.files[0];
@@ -493,19 +491,6 @@ function UserRules() {
}); });
}; };
useEffect(() => {
(async () => {
if (selectedSub?.url) {
try {
const rules = await loadSubRules(selectedSub?.url);
setSubRules(rules);
} catch (err) {
console.log("[load rules]", err);
}
}
})();
}, [selectedSub?.url]);
useEffect(() => { useEffect(() => {
if (!showAdd) { if (!showAdd) {
setKeyword(""); setKeyword("");
@@ -514,7 +499,13 @@ function UserRules() {
return ( return (
<Stack spacing={3}> <Stack spacing={3}>
<Stack direction="row" alignItems="center" spacing={2} useFlexGap flexWrap="wrap"> <Stack
direction="row"
alignItems="center"
spacing={2}
useFlexGap
flexWrap="wrap"
>
<Button <Button
size="small" size="small"
variant="contained" variant="contained"
@@ -536,7 +527,7 @@ function UserRules() {
<ShareButton <ShareButton
rules={rules} rules={rules}
injectRules={injectRules} injectRules={injectRules}
selectedSub={selectedSub} selectedUrl={selectedUrl}
/> />
<FormControlLabel <FormControlLabel
@@ -572,7 +563,7 @@ function UserRules() {
{injectRules && ( {injectRules && (
<Box> <Box>
{subRules {selectedRules
.filter( .filter(
(rule) => (rule) =>
rule.pattern.includes(keyword) || keyword.includes(rule.pattern) rule.pattern.includes(keyword) || keyword.includes(rule.pattern)
@@ -586,13 +577,13 @@ function UserRules() {
); );
} }
function SubRulesItem({ index, url, selectedUrl, subrules, setRules }) { function SubRulesItem({ index, url, selectedUrl, delSub, setSelectedRules }) {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const handleDel = async () => { const handleDel = async () => {
try { try {
await subrules.del(url); await delSub(url);
await rulesCache.del(url); await delSubRules(url);
} catch (err) { } catch (err) {
console.log("[del subrules]", err); console.log("[del subrules]", err);
} }
@@ -603,7 +594,7 @@ function SubRulesItem({ index, url, selectedUrl, subrules, setRules }) {
setLoading(true); setLoading(true);
const rules = await syncSubRules(url); const rules = await syncSubRules(url);
if (rules.length > 0 && url === selectedUrl) { if (rules.length > 0 && url === selectedUrl) {
setRules(rules); setSelectedRules(rules);
} }
} catch (err) { } catch (err) {
console.log("[sync sub rules]", err); console.log("[sync sub rules]", err);
@@ -633,7 +624,7 @@ function SubRulesItem({ index, url, selectedUrl, subrules, setRules }) {
); );
} }
function SubRulesEdit({ subrules }) { function SubRulesEdit({ subList, addSub }) {
const i18n = useI18n(); const i18n = useI18n();
const [inputText, setInputText] = useState(""); const [inputText, setInputText] = useState("");
const [inputError, setInputError] = useState(""); const [inputError, setInputError] = useState("");
@@ -656,7 +647,7 @@ function SubRulesEdit({ subrules }) {
return; return;
} }
if (subrules.list.find((item) => item.url === url)) { if (subList.find((item) => item.url === url)) {
setInputError(i18n("error_duplicate_values")); setInputError(i18n("error_duplicate_values"));
return; return;
} }
@@ -667,7 +658,7 @@ function SubRulesEdit({ subrules }) {
if (rules.length === 0) { if (rules.length === 0) {
throw new Error("empty rules"); throw new Error("empty rules");
} }
await subrules.add(url); await addSub(url);
setShowInput(false); setShowInput(false);
setInputText(""); setInputText("");
} catch (err) { } catch (err) {
@@ -735,47 +726,36 @@ function SubRulesEdit({ subrules }) {
); );
} }
function SubRules() { function SubRules({ subRules }) {
const [loading, setLoading] = useState(false); const {
const [rules, setRules] = useState([]); subList,
const subrules = useSubrules(); selectSub,
const selectedSub = subrules.list.find((item) => item.selected); addSub,
delSub,
selectedUrl,
selectedRules,
setSelectedRules,
loading,
} = subRules;
const handleSelect = (e) => { const handleSelect = (e) => {
const url = e.target.value; const url = e.target.value;
subrules.select(url); selectSub(url);
}; };
useEffect(() => {
(async () => {
if (selectedSub?.url) {
try {
setLoading(true);
const rules = await loadSubRules(selectedSub?.url);
setRules(rules);
} catch (err) {
console.log("[load rules]", err);
} finally {
setLoading(false);
}
}
})();
}, [selectedSub?.url]);
return ( return (
<Stack spacing={3}> <Stack spacing={3}>
<SubRulesEdit subrules={subrules} /> <SubRulesEdit subList={subList} addSub={addSub} />
<RadioGroup value={selectedSub?.url} onChange={handleSelect}> <RadioGroup value={selectedUrl} onChange={handleSelect}>
{subrules.list.map((item, index) => ( {subList.map((item, index) => (
<SubRulesItem <SubRulesItem
key={item.url} key={item.url}
url={item.url} url={item.url}
index={index} index={index}
selectedUrl={selectedSub?.url} selectedUrl={selectedUrl}
subrules={subrules} delSub={delSub}
setRules={setRules} setSelectedRules={setSelectedRules}
/> />
))} ))}
</RadioGroup> </RadioGroup>
@@ -786,7 +766,9 @@ function SubRules() {
<CircularProgress /> <CircularProgress />
</center> </center>
) : ( ) : (
rules.map((rule) => <RuleAccordion key={rule.pattern} rule={rule} />) selectedRules.map((rule) => (
<RuleAccordion key={rule.pattern} rule={rule} />
))
)} )}
</Box> </Box>
</Stack> </Stack>
@@ -796,6 +778,7 @@ function SubRules() {
export default function Rules() { export default function Rules() {
const i18n = useI18n(); const i18n = useI18n();
const [activeTab, setActiveTab] = useState(0); const [activeTab, setActiveTab] = useState(0);
const subRules = useSubRules();
const handleTabChange = (e, newValue) => { const handleTabChange = (e, newValue) => {
setActiveTab(newValue); setActiveTab(newValue);
@@ -816,8 +799,12 @@ export default function Rules() {
<Tab label={i18n("subscribe_rules")} /> <Tab label={i18n("subscribe_rules")} />
</Tabs> </Tabs>
</Box> </Box>
<div hidden={activeTab !== 0}>{activeTab === 0 && <UserRules />}</div> <div hidden={activeTab !== 0}>
<div hidden={activeTab !== 1}>{activeTab === 1 && <SubRules />}</div> {activeTab === 0 && <UserRules subRules={subRules} />}
</div>
<div hidden={activeTab !== 1}>
{activeTab === 1 && <SubRules subRules={subRules} />}
</div>
</Stack> </Stack>
</Box> </Box>
); );

View File

@@ -6,18 +6,15 @@ 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 } from "../../hooks/Setting"; import { useSetting } from "../../hooks/Setting";
import { limitNumber, debounce } from "../../libs/utils"; import { limitNumber } from "../../libs/utils";
import { useI18n } from "../../hooks/I18n"; import { useI18n } from "../../hooks/I18n";
import { UI_LANGS } from "../../config"; import { UI_LANGS } from "../../config";
import { useMemo } from "react";
export default function Settings() { export default function Settings() {
const i18n = useI18n(); const i18n = useI18n();
const { setting, updateSetting } = useSetting(); const { setting, updateSetting } = useSetting();
const handleChange = useMemo( const handleChange = (e) => {
() =>
debounce((e) => {
e.preventDefault(); e.preventDefault();
let { name, value } = e.target; let { name, value } = e.target;
switch (name) { switch (name) {
@@ -38,9 +35,7 @@ export default function Settings() {
updateSetting({ updateSetting({
[name]: value, [name]: value,
}); });
}, 500), };
[updateSetting]
);
if (!setting) { if (!setting) {
return; return;
@@ -84,7 +79,7 @@ export default function Settings() {
label={i18n("fetch_limit")} label={i18n("fetch_limit")}
type="number" type="number"
name="fetchLimit" name="fetchLimit"
defaultValue={fetchLimit} value={fetchLimit}
onChange={handleChange} onChange={handleChange}
/> />
@@ -93,7 +88,7 @@ export default function Settings() {
label={i18n("fetch_interval")} label={i18n("fetch_interval")}
type="number" type="number"
name="fetchInterval" name="fetchInterval"
defaultValue={fetchInterval} value={fetchInterval}
onChange={handleChange} onChange={handleChange}
/> />
@@ -102,7 +97,7 @@ export default function Settings() {
label={i18n("min_translate_length")} label={i18n("min_translate_length")}
type="number" type="number"
name="minLength" name="minLength"
defaultValue={minLength} value={minLength}
onChange={handleChange} onChange={handleChange}
/> />
@@ -111,7 +106,7 @@ export default function Settings() {
label={i18n("max_translate_length")} label={i18n("max_translate_length")}
type="number" type="number"
name="maxLength" name="maxLength"
defaultValue={maxLength} value={maxLength}
onChange={handleChange} onChange={handleChange}
/> />
@@ -132,7 +127,7 @@ export default function Settings() {
size="small" size="small"
label={i18n("google_api")} label={i18n("google_api")}
name="googleUrl" name="googleUrl"
defaultValue={googleUrl} value={googleUrl}
onChange={handleChange} onChange={handleChange}
/> />
@@ -140,7 +135,7 @@ export default function Settings() {
size="small" size="small"
label={i18n("openai_api")} label={i18n("openai_api")}
name="openaiUrl" name="openaiUrl"
defaultValue={openaiUrl} value={openaiUrl}
onChange={handleChange} onChange={handleChange}
/> />
@@ -149,7 +144,7 @@ export default function Settings() {
type="password" type="password"
label={i18n("openai_key")} label={i18n("openai_key")}
name="openaiKey" name="openaiKey"
defaultValue={openaiKey} value={openaiKey}
onChange={handleChange} onChange={handleChange}
/> />
@@ -157,7 +152,7 @@ export default function Settings() {
size="small" size="small"
label={i18n("openai_model")} label={i18n("openai_model")}
name="openaiModel" name="openaiModel"
defaultValue={openaiModel} value={openaiModel}
onChange={handleChange} onChange={handleChange}
/> />
@@ -165,7 +160,7 @@ export default function Settings() {
size="small" size="small"
label={i18n("openai_prompt")} label={i18n("openai_prompt")}
name="openaiPrompt" name="openaiPrompt"
defaultValue={openaiPrompt} value={openaiPrompt}
onChange={handleChange} onChange={handleChange}
multiline multiline
/> />

View File

@@ -6,8 +6,7 @@ import { useSync } from "../../hooks/Sync";
import Alert from "@mui/material/Alert"; import Alert from "@mui/material/Alert";
import Link from "@mui/material/Link"; import Link from "@mui/material/Link";
import { URL_KISS_WORKER } from "../../config"; import { URL_KISS_WORKER } from "../../config";
import { debounce } from "../../libs/utils"; import { useState } from "react";
import { useMemo, useState } from "react";
import { syncAll } from "../../libs/sync"; import { syncAll } from "../../libs/sync";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import { useAlert } from "../../hooks/Alert"; import { useAlert } from "../../hooks/Alert";
@@ -16,22 +15,17 @@ import CircularProgress from "@mui/material/CircularProgress";
export default function SyncSetting() { export default function SyncSetting() {
const i18n = useI18n(); const i18n = useI18n();
const sync = useSync(); const { sync, updateSync } = useSync();
const alert = useAlert(); const alert = useAlert();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const handleChange = useMemo( const handleChange = async (e) => {
() =>
debounce(async (e) => {
e.preventDefault(); e.preventDefault();
const { name, value } = e.target; const { name, value } = e.target;
await sync.update({ await updateSync({
[name]: value, [name]: value,
}); });
// trySyncAll(); };
}, 500),
[sync]
);
const handleSyncTest = async (e) => { const handleSyncTest = async (e) => {
e.preventDefault(); e.preventDefault();
@@ -47,11 +41,7 @@ export default function SyncSetting() {
} }
}; };
if (!sync.opt) { const { syncUrl, syncKey } = sync;
return;
}
const { syncUrl, syncKey } = sync.opt;
return ( return (
<Box> <Box>
@@ -62,7 +52,7 @@ export default function SyncSetting() {
size="small" size="small"
label={i18n("data_sync_url")} label={i18n("data_sync_url")}
name="syncUrl" name="syncUrl"
defaultValue={syncUrl} value={syncUrl}
onChange={handleChange} onChange={handleChange}
helperText={ helperText={
<Link href={URL_KISS_WORKER}>{i18n("about_sync_api")}</Link> <Link href={URL_KISS_WORKER}>{i18n("about_sync_api")}</Link>
@@ -74,11 +64,17 @@ export default function SyncSetting() {
type="password" type="password"
label={i18n("data_sync_key")} label={i18n("data_sync_key")}
name="syncKey" name="syncKey"
defaultValue={syncKey} value={syncKey}
onChange={handleChange} onChange={handleChange}
/> />
<Stack direction="row" alignItems="center" spacing={2} useFlexGap flexWrap="wrap"> <Stack
direction="row"
alignItems="center"
spacing={2}
useFlexGap
flexWrap="wrap"
>
<Button <Button
size="small" size="small"
variant="contained" variant="contained"