diff --git a/README.en.md b/README.en.md index 5cf1fee..2363c88 100644 --- a/README.en.md +++ b/README.en.md @@ -33,6 +33,7 @@ If you also like a little more simplicity, welcome to pick it up. - [ ] DeepL - [ ] Upload to app Store - [x] Open source +- [x] Data Synchronization Function ### Guide @@ -42,3 +43,7 @@ cd kiss-translator yarn yarn dist ``` + +### Data Sync + +Goto: [https://github.com/fishjar/kiss-worker](https://github.com/fishjar/kiss-worker) diff --git a/README.md b/README.md index f3d6d3e..41d3cd4 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ - [ ] DeepL - [ ] 上架应用市场 - [x] 开放源代码 +- [x] 数据同步功能 ### 指引 @@ -42,3 +43,7 @@ cd kiss-translator yarn yarn dist ``` + +### 数据同步 + +移步: [https://github.com/fishjar/kiss-worker](https://github.com/fishjar/kiss-worker) diff --git a/src/apis/data.js b/src/apis/data.js deleted file mode 100644 index 60ecd29..0000000 --- a/src/apis/data.js +++ /dev/null @@ -1,79 +0,0 @@ -import { fetchPolyfill } from "../libs/fetch"; -import { - KV_HEADER_KEY, - KV_RULES_KEY, - KV_SETTING_KEY, - STOKEY_RULES, - STOKEY_SETTING, - STOKEY_RULES_UPDATE_AT, -} from "../config"; -import { getSetting, getRules } from "../libs"; -import storage from "../libs/storage"; - -/** - * 同步数据 - * @param {*} param0 - * @returns - */ -const apiSyncData = async ({ key, value, updateAt }) => { - const { syncUrl, syncKey } = await getSetting(); - if (!syncUrl || !syncKey) { - console.log("data sync should set the api and key"); - return; - } - return fetchPolyfill(syncUrl, { - headers: { - "Content-type": "application/json", - [KV_HEADER_KEY]: syncKey, - }, - method: "POST", - body: JSON.stringify({ key, value, updateAt }), - }); -}; - -/** - * 同步rules - * @param {*} value - * @param {*} updateAt - */ -export const apiSyncRules = async (value, updateAt) => { - const res = await apiSyncData({ - key: KV_RULES_KEY, - value, - updateAt, - }); - console.log("res", res); - if (res && res.updateAt > updateAt) { - await storage.setObj(STOKEY_RULES, res.value); - await storage.setObj(STOKEY_RULES_UPDATE_AT, res.updateAt); - } -}; - -/** - * 同步setting - * @param {*} value - * @param {*} updateAt - */ -export const apiSyncSetting = async (value, updateAt) => { - const res = await apiSyncData({ - key: KV_SETTING_KEY, - value, - updateAt, - }); - console.log("res", res); - if (res && res.updateAt > updateAt) { - await storage.setObj(STOKEY_SETTING, res.value); - } -}; - -/** - * 同步全部数据 - */ -export const apiSyncAll = async () => { - const setting = await getSetting(); - const rules = await getRules(); - const settingUpdateAt = setting.updateAt; - const rulesUpdateAt = (await storage.getObj(STOKEY_RULES_UPDATE_AT)) || 1; - await apiSyncSetting(setting, settingUpdateAt); - await apiSyncRules(rules, rulesUpdateAt); -}; diff --git a/src/apis/index.js b/src/apis/index.js index c65e8f2..196c3db 100644 --- a/src/apis/index.js +++ b/src/apis/index.js @@ -8,9 +8,27 @@ import { OPT_LANGS_SPECIAL, PROMPT_PLACE_FROM, PROMPT_PLACE_TO, + KV_HEADER_KEY, } from "../config"; import { getSetting, detectLang } from "../libs"; +/** + * 同步数据 + * @param {*} url + * @param {*} key + * @param {*} data + * @returns + */ +export const apiSyncData = async (url, key, data) => + fetchPolyfill(url, { + headers: { + "Content-type": "application/json", + [KV_HEADER_KEY]: key, + }, + method: "POST", + body: JSON.stringify(data), + }); + /** * 谷歌翻译 * @param {*} text diff --git a/src/background.js b/src/background.js index 73958d8..3754a24 100644 --- a/src/background.js +++ b/src/background.js @@ -4,14 +4,16 @@ import { MSG_FETCH_LIMIT, DEFAULT_SETTING, DEFAULT_RULES, + DEFAULT_SYNC, STOKEY_SETTING, STOKEY_RULES, + STOKEY_SYNC, CACHE_NAME, } from "./config"; import { fetchData, setFetchLimit } from "./libs/fetch"; import storage from "./libs/storage"; import { getSetting } from "./libs"; -import { apiSyncAll } from "./apis/data"; +import { syncAll } from "./libs/sync"; /** * 插件安装 @@ -20,6 +22,7 @@ browser.runtime.onInstalled.addListener(() => { console.log("onInstalled"); storage.trySetObj(STOKEY_SETTING, DEFAULT_SETTING); storage.trySetObj(STOKEY_RULES, DEFAULT_RULES); + storage.trySetObj(STOKEY_SYNC, DEFAULT_SYNC); }); /** @@ -29,11 +32,7 @@ browser.runtime.onStartup.addListener(async () => { console.log("onStartup"); // 同步数据 - try { - await apiSyncAll(); - } catch (err) { - console.log("[sync all]", err); - } + await syncAll(); // 清除缓存 const { clearCache } = await getSetting(); diff --git a/src/config/i18n.js b/src/config/i18n.js index 0d0de2b..0e66ce6 100644 --- a/src/config/i18n.js +++ b/src/config/i18n.js @@ -22,6 +22,10 @@ export const I18N = { zh: `规则设置`, en: `Rules Setting`, }, + sync_setting: { + zh: `同步设置`, + en: `Sync Setting`, + }, about: { zh: `关于`, en: `About`, @@ -94,9 +98,13 @@ export const I18N = { zh: `添加`, en: `Add`, }, - advanced_warn: { - zh: `如不明白,谨慎修改!不同的浏览器,选择器规则不一定通用。`, - en: `If you don't understand, modify it carefully! Different browsers, the selector rules are not necessarily universal.`, + sync_warn: { + zh: `数据同步功能只是按修改时间简单覆盖,谨慎使用!`, + en: `The data synchronization function is simply overwritten according to the modification time, use it with caution!`, + }, + about_sync_api: { + zh: `查看关于数据同步接口部署`, + en: `View About Data Synchronization Interface Deployment`, }, style_none: { zh: `无`, @@ -139,8 +147,8 @@ export const I18N = { en: `Multiple URLs can be separated by English commas ","`, }, selector_helper: { - zh: `遵循CSS选择器规则,但不同浏览器,可能支持不同,有些不同的写法。`, - en: `Follow the CSS selector rules, but different browsers may support different, and some have different ways of writing.`, + zh: `遵循CSS选择器规则,但不同浏览器,有些不同的写法。`, + en: `Follow the CSS selector rules, but different browsers have some different ways of writing.`, }, translate_switch: { zh: `开启翻译`, diff --git a/src/config/index.js b/src/config/index.js index c0032d2..1636db9 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -8,7 +8,7 @@ export const APP_LCNAME = APP_NAME.toLowerCase(); export const STOKEY_MSAUTH = `${APP_NAME}_msauth`; export const STOKEY_SETTING = `${APP_NAME}_setting`; export const STOKEY_RULES = `${APP_NAME}_rules`; -export const STOKEY_RULES_UPDATE_AT = `${APP_NAME}_rules_update_at`; +export const STOKEY_SYNC = `${APP_NAME}_sync`; export const KV_HEADER_KEY = "X-KISS-PSK"; export const KV_RULES_KEY = "KT_RULES"; @@ -26,6 +26,7 @@ export const THEME_LIGHT = "light"; export const THEME_DARK = "dark"; export const URL_APP_HOMEPAGE = "https://github.com/fishjar/kiss-translator"; +export const URL_KISS_WORKER = "https://github.com/fishjar/kiss-worker"; export const URL_RAW_PREFIX = "https://raw.githubusercontent.com/fishjar/kiss-translator/master"; export const URL_MICROSOFT_AUTH = "https://edge.microsoft.com/translate/auth"; @@ -135,9 +136,6 @@ export const DEFAULT_SETTING = { openaiKey: "", openaiModel: "gpt-4", openaiPrompt: `You will be provided with a sentence in ${PROMPT_PLACE_FROM}, and your task is to translate it into ${PROMPT_PLACE_TO}.`, - syncUrl: "", // 数据同步接口 - syncKey: "", // 数据同步密钥 - updateAt: 1, // 更新时间 }; export const DEFAULT_RULES = [ @@ -151,3 +149,12 @@ export const DEFAULT_RULES = [ export const TRANS_MIN_LENGTH = 5; // 最短翻译长度 export const TRANS_MAX_LENGTH = 5000; // 最长翻译长度 + +export const DEFAULT_SYNC = { + syncUrl: "", // 数据同步接口 + syncKey: "", // 数据同步密钥 + settingUpdateAt: 0, + settingSyncAt: 0, + rulesUpdateAt: 0, + rulesSyncAt: 0, +}; diff --git a/src/hooks/Rules.js b/src/hooks/Rules.js index e0f606b..8fb77da 100644 --- a/src/hooks/Rules.js +++ b/src/hooks/Rules.js @@ -4,12 +4,12 @@ import { OPT_STYLE_ALL, OPT_LANGS_FROM, OPT_LANGS_TO, - STOKEY_RULES_UPDATE_AT, } from "../config"; import storage from "../libs/storage"; import { useStorages } from "./Storage"; import { matchValue } from "../libs/utils"; -import { apiSyncRules } from "../apis/data"; +import { syncRules } from "../libs/sync"; +import { useSync } from "./Sync"; /** * 匹配规则增删改查 hook @@ -18,13 +18,14 @@ import { apiSyncRules } from "../apis/data"; export function useRules() { const storages = useStorages(); const list = storages?.[STOKEY_RULES] || []; + const sync = useSync(); const update = async (rules) => { - const now = Date.now(); + const updateAt = sync.opt?.rulesUpdateAt ? Date.now() : 0; await storage.setObj(STOKEY_RULES, rules); - await storage.setObj(STOKEY_RULES_UPDATE_AT, now); + await sync.update({ rulesUpdateAt: updateAt }); try { - await apiSyncRules(rules, now); + await syncRules(); } catch (err) { console.log("[sync rules]", err); } diff --git a/src/hooks/Setting.js b/src/hooks/Setting.js index b228832..a5cc8d7 100644 --- a/src/hooks/Setting.js +++ b/src/hooks/Setting.js @@ -1,7 +1,7 @@ -import { useCallback } from "react"; import { STOKEY_SETTING } from "../config"; import storage from "../libs/storage"; import { useStorages } from "./Storage"; +import { useSync } from "./Sync"; /** * 设置hook @@ -17,7 +17,10 @@ export function useSetting() { * @returns */ export function useSettingUpdate() { - return useCallback(async (obj) => { - await storage.putObj(STOKEY_SETTING, { ...obj, updateAt: Date.now() }); - }, []); + 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 }); + }; } diff --git a/src/hooks/Storage.js b/src/hooks/Storage.js index e4dcf10..78c8beb 100644 --- a/src/hooks/Storage.js +++ b/src/hooks/Storage.js @@ -4,9 +4,10 @@ import { STOKEY_SETTING, STOKEY_RULES, STOKEY_MSAUTH, + STOKEY_SYNC, DEFAULT_SETTING, DEFAULT_RULES, - STOKEY_RULES_UPDATE_AT, + DEFAULT_SYNC, } from "../config"; import storage from "../libs/storage"; @@ -17,7 +18,7 @@ export const defaultStorage = { [STOKEY_MSAUTH]: null, [STOKEY_SETTING]: DEFAULT_SETTING, [STOKEY_RULES]: DEFAULT_RULES, - [STOKEY_RULES_UPDATE_AT]: 1, + [STOKEY_SYNC]: DEFAULT_SYNC, }; const StoragesContext = createContext(null); diff --git a/src/hooks/Sync.js b/src/hooks/Sync.js new file mode 100644 index 0000000..c74aa1e --- /dev/null +++ b/src/hooks/Sync.js @@ -0,0 +1,20 @@ +import { useCallback } from "react"; +import { STOKEY_SYNC } from "../config"; +import storage from "../libs/storage"; +import { useStorages } 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, + }; +} diff --git a/src/libs/sync.js b/src/libs/sync.js new file mode 100644 index 0000000..38d590e --- /dev/null +++ b/src/libs/sync.js @@ -0,0 +1,74 @@ +import { + STOKEY_SYNC, + DEFAULT_SYNC, + KV_SETTING_KEY, + KV_RULES_KEY, + STOKEY_SETTING, + STOKEY_RULES, +} from "../config"; +import storage from "../libs/storage"; +import { getSetting, getRules } from "."; +import { apiSyncData } from "../apis"; + +const loadOpt = async () => (await storage.getObj(STOKEY_SYNC)) || DEFAULT_SYNC; + +export const syncSetting = async () => { + const { syncUrl, syncKey, settingUpdateAt } = await loadOpt(); + if (!syncUrl || !syncKey) { + return; + } + + const setting = await getSetting(); + const res = await apiSyncData(syncUrl, syncKey, { + key: KV_SETTING_KEY, + value: setting, + updateAt: settingUpdateAt, + }); + + if (res && res.updateAt > settingUpdateAt) { + await storage.putObj(STOKEY_SYNC, { + settingUpdateAt: res.updateAt, + settingSyncAt: res.updateAt, + }); + await storage.setObj(STOKEY_SETTING, res.value); + } else { + await storage.putObj(STOKEY_SYNC, { + settingSyncAt: res.updateAt, + }); + } +}; + +export const syncRules = async () => { + const { syncUrl, syncKey, rulesUpdateAt } = await loadOpt(); + if (!syncUrl || !syncKey) { + return; + } + + const rules = await getRules(); + const res = await apiSyncData(syncUrl, syncKey, { + key: KV_RULES_KEY, + value: rules, + updateAt: rulesUpdateAt, + }); + + if (res && res.updateAt > rulesUpdateAt) { + await storage.putObj(STOKEY_SYNC, { + rulesUpdateAt: res.updateAt, + rulesSyncAt: res.updateAt, + }); + await storage.setObj(STOKEY_RULES, res.value); + } else { + await storage.putObj(STOKEY_SYNC, { + rulesSyncAt: res.updateAt, + }); + } +}; + +export const syncAll = async () => { + try { + await syncSetting(); + await syncRules(); + } catch (err) { + console.log("[sync all]", err); + } +}; diff --git a/src/views/Options/Layout.js b/src/views/Options/Layout.js index b811f47..8a98419 100644 --- a/src/views/Options/Layout.js +++ b/src/views/Options/Layout.js @@ -6,6 +6,7 @@ import Box from "@mui/material/Box"; import Navigator from "./Navigator"; import Header from "./Header"; import { useTheme } from "@mui/material/styles"; +import { syncAll } from "../../libs/sync"; export default function Layout() { const navWidth = 256; @@ -20,6 +21,7 @@ export default function Layout() { useEffect(() => { setOpen(false); + syncAll(); }, [location]); return ( diff --git a/src/views/Options/Navigator.js b/src/views/Options/Navigator.js index b651ef9..e4b1b88 100644 --- a/src/views/Options/Navigator.js +++ b/src/views/Options/Navigator.js @@ -9,6 +9,7 @@ import SettingsIcon from "@mui/icons-material/Settings"; import InfoIcon from "@mui/icons-material/Info"; import DesignServicesIcon from "@mui/icons-material/DesignServices"; import { useI18n } from "../../hooks/I18n"; +import SyncIcon from "@mui/icons-material/Sync"; function LinkItem({ label, url, icon }) { const match = useMatch(url); @@ -35,6 +36,12 @@ export default function Navigator(props) { url: "/rules", icon: , }, + { + id: "sync", + label: i18n("sync_setting"), + url: "/sync", + icon: , + }, { id: "about", label: i18n("about"), url: "/about", icon: }, ]; return ( diff --git a/src/views/Options/Rules.js b/src/views/Options/Rules.js index 2408c60..376438b 100644 --- a/src/views/Options/Rules.js +++ b/src/views/Options/Rules.js @@ -10,7 +10,6 @@ import { OPT_STYLE_ALL, } from "../../config"; import { useState, useRef } from "react"; -import Alert from "@mui/material/Alert"; import { useI18n } from "../../hooks/I18n"; import Typography from "@mui/material/Typography"; import Accordion from "@mui/material/Accordion"; @@ -354,8 +353,6 @@ export default function Rules() { return ( - {i18n("advanced_warn")} -