Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
471a4a3159 | ||
|
|
a2f99da3b4 | ||
|
|
accab22d56 | ||
|
|
6ea5228a5f | ||
|
|
a07d2cafb6 | ||
|
|
1b38f19cc1 | ||
|
|
aa5b286e0b | ||
|
|
6b6bbed330 | ||
|
|
489bc9534b | ||
|
|
01ebc184ad | ||
|
|
f591d66365 | ||
|
|
80782287d8 | ||
|
|
3494bb1297 | ||
|
|
92ffda5220 | ||
|
|
fbaeff6b7b | ||
|
|
248d3726dd | ||
|
|
1553559b1a | ||
|
|
8935ced75a | ||
|
|
a865d6d74f | ||
|
|
6d976554fd |
2
.env
2
.env
@@ -2,7 +2,7 @@ GENERATE_SOURCEMAP=false
|
||||
|
||||
REACT_APP_NAME=KISS Translator
|
||||
REACT_APP_NAME_CN=简约翻译
|
||||
REACT_APP_VERSION=1.7.1
|
||||
REACT_APP_VERSION=1.7.2
|
||||
|
||||
REACT_APP_HOMEPAGE=https://github.com/fishjar/kiss-translator
|
||||
|
||||
|
||||
9
.github/workflows/release.yml
vendored
9
.github/workflows/release.yml
vendored
@@ -10,12 +10,15 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8.7.6
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "18.17.0"
|
||||
cache: "yarn"
|
||||
- run: yarn install
|
||||
- run: yarn build
|
||||
cache: "pnpm"
|
||||
- run: pnpm install
|
||||
- run: pnpm build
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: build-artifacts
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
nodeLinker: node-modules
|
||||
27
README.en.md
27
README.en.md
@@ -24,11 +24,17 @@ If you also like a little more simplicity, welcome to pick it up.
|
||||
- [x] Supports multiple translation services
|
||||
- [x] Google/Microsoft/DeepL/OpenAI
|
||||
- [x] Custom translation interface
|
||||
- [x] Support input box translation
|
||||
- [x] Support YouTube subtitle translation
|
||||
- [x] Data synchronization function
|
||||
- [x] Custom rules + rule subscription
|
||||
- [x] Custom style
|
||||
- [x] Covers common translation scenarios
|
||||
- [x] Web bilingual translation
|
||||
- [x] Input box translation
|
||||
- [x] Mouseover translation
|
||||
- [x] YouTube subtitle translation
|
||||
- [x] Cross-client data synchronization
|
||||
- [x] KISS-Worker(cloudflare/docker)
|
||||
- [x] WebDAV
|
||||
- [x] Custom translation rules
|
||||
- [x] Rule subscription/rule sharing
|
||||
- [x] Custom translation style
|
||||
- [x] Custom shortcut keys
|
||||
- `Alt+Q` Toggle Translation
|
||||
- `Alt+C` Toggle Styles
|
||||
@@ -36,7 +42,12 @@ If you also like a little more simplicity, welcome to pick it up.
|
||||
- `Alt+O` Open Options
|
||||
- `Alt+I` Input Box Translation
|
||||
|
||||
## Download
|
||||
## Install
|
||||
|
||||
> Note: For the following reasons, it is recommended to use browser extensions first
|
||||
>
|
||||
> - Browser extension can use local language recognition
|
||||
> - Grease Monkey script will encounter more usage problems
|
||||
|
||||
- [x] Browser extension
|
||||
- [x] Chrome [Installation address](https://chrome.google.com/webstore/detail/kiss-translator/bdiifdefkgmcblbcghdlonllpjhhjgof?hl=zh-CN)
|
||||
@@ -73,8 +84,8 @@ If you also like a little more simplicity, welcome to pick it up.
|
||||
```sh
|
||||
git clone https://github.com/fishjar/kiss-translator.git
|
||||
cd kiss-translator
|
||||
yarn install
|
||||
yarn build
|
||||
pnpm install
|
||||
pnpm build
|
||||
```
|
||||
|
||||
## Discussion
|
||||
|
||||
31
README.md
31
README.md
@@ -1,6 +1,6 @@
|
||||
# 简约翻译
|
||||
|
||||
一个简约的 [双语网页翻译扩展 & 油猴脚本](https://github.com/fishjar/kiss-translator)。
|
||||
一个简约的 [网页双语翻译扩展 & 油猴脚本](https://github.com/fishjar/kiss-translator)。
|
||||
|
||||
[kiss-translator.webm](https://github.com/fishjar/kiss-translator/assets/1157624/f7ba8a5c-e4a8-4d5a-823a-5c5c67a0a47f)
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
如果你也喜欢简约一点的,欢迎自取。
|
||||
|
||||
## 特点
|
||||
## 特性
|
||||
|
||||
- [x] 保持简约
|
||||
- [x] 开放源代码
|
||||
@@ -24,11 +24,17 @@
|
||||
- [x] 支持多种翻译服务
|
||||
- [x] Google/Microsoft/DeepL/OpenAI
|
||||
- [x] 自定义翻译接口
|
||||
- [x] 支持输入框翻译
|
||||
- [x] 支持 YouTube 字幕翻译
|
||||
- [x] 数据同步功能
|
||||
- [x] 自定义规则 + 规则订阅
|
||||
- [x] 自定义样式
|
||||
- [x] 覆盖常见翻译场景
|
||||
- [x] 网页双语翻译
|
||||
- [x] 输入框翻译
|
||||
- [x] 鼠标悬停翻译
|
||||
- [x] YouTube 字幕翻译
|
||||
- [x] 跨客户端数据同步
|
||||
- [x] KISS-Worker(cloudflare/docker)
|
||||
- [x] WebDAV
|
||||
- [x] 自定义翻译规则
|
||||
- [x] 规则订阅/规则分享
|
||||
- [x] 自定义译文样式
|
||||
- [x] 自定义快捷键
|
||||
- `Alt+Q` 开启翻译
|
||||
- `Alt+C` 切换样式
|
||||
@@ -36,7 +42,12 @@
|
||||
- `Alt+O` 打开设置
|
||||
- `Alt+I` 输入框翻译
|
||||
|
||||
## 下载
|
||||
## 安装
|
||||
|
||||
> 注:基于以下原因,建议优先使用浏览器扩展
|
||||
>
|
||||
> - 浏览器扩展可以使用本地的语言识别
|
||||
> - 油猴脚本会遇到更多使用上的问题
|
||||
|
||||
- [x] 浏览器扩展
|
||||
- [x] Chrome [安装地址](https://chrome.google.com/webstore/detail/kiss-translator/bdiifdefkgmcblbcghdlonllpjhhjgof?hl=zh-CN)
|
||||
@@ -73,8 +84,8 @@
|
||||
```sh
|
||||
git clone https://github.com/fishjar/kiss-translator.git
|
||||
cd kiss-translator
|
||||
yarn install
|
||||
yarn build
|
||||
pnpm install
|
||||
pnpm build
|
||||
```
|
||||
|
||||
## 交流
|
||||
|
||||
@@ -75,7 +75,7 @@ const userscriptWebpack = (config, env) => {
|
||||
// @name ${process.env.REACT_APP_NAME}
|
||||
// @namespace ${process.env.REACT_APP_HOMEPAGE}
|
||||
// @version ${process.env.REACT_APP_VERSION}
|
||||
// @description A minimalist bilingual translation Extension & Greasemonkey Script (一个简约的双语网页翻译扩展 & 油猴脚本)
|
||||
// @description A minimalist bilingual translation Extension & Greasemonkey Script (一个简约的网页双语翻译扩展 & 油猴脚本)
|
||||
// @author Gabe<yugang2002@gmail.com>
|
||||
// @homepageURL ${process.env.REACT_APP_HOMEPAGE}
|
||||
// @license GPL-3.0
|
||||
@@ -102,6 +102,7 @@ const userscriptWebpack = (config, env) => {
|
||||
// @connect githubusercontent.com
|
||||
// @connect kiss-translator.rayjar.com
|
||||
// @connect ghproxy.com
|
||||
// @connect dav.jianguoyun.com
|
||||
// @connect localhost:3000
|
||||
// @run-at document-end
|
||||
// ==/UserScript==
|
||||
|
||||
12
package.json
12
package.json
@@ -1,10 +1,11 @@
|
||||
{
|
||||
"name": "kiss-translator",
|
||||
"description": "A minimalist bilingual translation Extension & Greasemonkey Script",
|
||||
"version": "1.7.1",
|
||||
"version": "1.7.2",
|
||||
"author": "Gabe<yugang2002@gmail.com>",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@emotion/cache": "^11.11.0",
|
||||
"@emotion/react": "^11.11.1",
|
||||
"@emotion/styled": "^11.10.8",
|
||||
"@mui/icons-material": "^5.11.11",
|
||||
@@ -15,6 +16,7 @@
|
||||
"react-markdown": "^8.0.7",
|
||||
"react-router-dom": "^6.10.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"webdav": "^5.3.0",
|
||||
"webextension-polyfill": "^0.10.0"
|
||||
},
|
||||
"scripts": {
|
||||
@@ -27,7 +29,7 @@
|
||||
"build:userscript-ios": "file1=build/web/kiss-translator.user.js file2=build/web/kiss-translator-ios-safari.user.js && cp $file1 $file2 && sed -i 's|// @grant unsafeWindow|// @inject-into content|g' $file2",
|
||||
"build:userscript": "rm -rf build/userscript && mkdir build/userscript && cp build/web/*.user.js build/userscript/",
|
||||
"build:rules": "babel-node src/rules.js",
|
||||
"build": "yarn build:chrome && yarn build:edge && yarn build:firefox && yarn build:web && yarn build:userscript-ios && yarn build:userscript && yarn build:rules",
|
||||
"build": "pnpm build:chrome && pnpm build:edge && pnpm build:firefox && pnpm build:web && pnpm build:userscript-ios && pnpm build:userscript && pnpm build:rules",
|
||||
"deploy:web": "wrangler pages deploy ./build/web --project-name kiss-translator",
|
||||
"test": "react-app-rewired test",
|
||||
"eject": "react-scripts eject"
|
||||
@@ -39,7 +41,8 @@
|
||||
],
|
||||
"globals": {
|
||||
"GM": true,
|
||||
"unsafeWindow": true
|
||||
"unsafeWindow": true,
|
||||
"globalThis": true
|
||||
}
|
||||
},
|
||||
"browserslist": {
|
||||
@@ -61,5 +64,6 @@
|
||||
"@babel/preset-env": "^7.22.10",
|
||||
"react-app-rewired": "^2.2.1",
|
||||
"wrangler": "^3.4.0"
|
||||
}
|
||||
},
|
||||
"packageManager": "yarn@3.6.3"
|
||||
}
|
||||
|
||||
10738
pnpm-lock.yaml
generated
Normal file
10738
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@
|
||||
"message": "简约翻译"
|
||||
},
|
||||
"app_description": {
|
||||
"message": "一个简约的双语网页翻译扩展 & 油猴脚本"
|
||||
"message": "一个简约的网页双语翻译扩展 & 油猴脚本"
|
||||
},
|
||||
"toggle_translate": {
|
||||
"message": "开启翻译"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"manifest_version": 2,
|
||||
"name": "__MSG_app_name__",
|
||||
"description": "__MSG_app_description__",
|
||||
"version": "1.7.1",
|
||||
"version": "1.7.2",
|
||||
"default_locale": "en",
|
||||
"author": "Gabe<yugang2002@gmail.com>",
|
||||
"homepage_url": "https://github.com/fishjar/kiss-translator",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"manifest_version": 3,
|
||||
"name": "__MSG_app_name__",
|
||||
"description": "__MSG_app_description__",
|
||||
"version": "1.7.1",
|
||||
"version": "1.7.2",
|
||||
"default_locale": "en",
|
||||
"author": "Gabe<yugang2002@gmail.com>",
|
||||
"homepage_url": "https://github.com/fishjar/kiss-translator",
|
||||
|
||||
@@ -21,7 +21,7 @@ import { sha256 } from "../libs/utils";
|
||||
* @param {*} data
|
||||
* @returns
|
||||
*/
|
||||
export const apiSyncData = async (url, key, data, isBg = false) =>
|
||||
export const apiSyncData = async (url, key, data) =>
|
||||
fetchPolyfill(url, {
|
||||
headers: {
|
||||
"Content-type": "application/json",
|
||||
@@ -29,16 +29,14 @@ export const apiSyncData = async (url, key, data, isBg = false) =>
|
||||
},
|
||||
method: "POST",
|
||||
body: JSON.stringify(data),
|
||||
isBg,
|
||||
});
|
||||
|
||||
/**
|
||||
* 下载数据
|
||||
* @param {*} url
|
||||
* @param {*} isBg
|
||||
* @returns
|
||||
*/
|
||||
export const apiFetch = (url, isBg = false) => fetchPolyfill(url, { isBg });
|
||||
export const apiFetch = (url) => fetchPolyfill(url);
|
||||
|
||||
/**
|
||||
* 谷歌翻译
|
||||
|
||||
@@ -16,6 +16,8 @@ import { sendTabMsg } from "./libs/msg";
|
||||
import { trySyncAllSubRules } from "./libs/subRules";
|
||||
import { tryClearCaches } from "./libs";
|
||||
|
||||
globalThis.ContextType = "BACKGROUND";
|
||||
|
||||
/**
|
||||
* 插件安装
|
||||
*/
|
||||
@@ -30,7 +32,7 @@ browser.runtime.onStartup.addListener(async () => {
|
||||
console.log("browser onStartup");
|
||||
|
||||
// 同步数据
|
||||
await trySyncSettingAndRules(true);
|
||||
await trySyncSettingAndRules();
|
||||
|
||||
// 清除缓存
|
||||
const setting = await getSettingWithDefault();
|
||||
@@ -39,7 +41,7 @@ browser.runtime.onStartup.addListener(async () => {
|
||||
}
|
||||
|
||||
// 同步订阅规则
|
||||
trySyncAllSubRules(setting, true);
|
||||
trySyncAllSubRules(setting);
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@@ -435,10 +435,18 @@ export const I18N = {
|
||||
zh: `重启浏览器时清除缓存`,
|
||||
en: `Clear cache when restarting browser`,
|
||||
},
|
||||
data_sync_type: {
|
||||
zh: `数据同步方式`,
|
||||
en: `Data Sync Type`,
|
||||
},
|
||||
data_sync_url: {
|
||||
zh: `数据同步接口`,
|
||||
en: `Data Sync API`,
|
||||
},
|
||||
data_sync_user: {
|
||||
zh: `数据同步账户`,
|
||||
en: `Data Sync User`,
|
||||
},
|
||||
data_sync_key: {
|
||||
zh: `数据同步密钥`,
|
||||
en: `Data Sync Key`,
|
||||
@@ -460,8 +468,8 @@ export const I18N = {
|
||||
en: `Sorry, something went wrong!`,
|
||||
},
|
||||
error_sync_setting: {
|
||||
zh: `您的同步设置未填写,无法在线分享。`,
|
||||
en: `Your sync settings are missing and cannot be shared online.`,
|
||||
zh: `您的同步类型必须为“KISS-Worker”,且需填写完整`,
|
||||
en: `Your sync type must be "KISS-Worker" and must be filled in completely`,
|
||||
},
|
||||
click_test: {
|
||||
zh: `点击测试`,
|
||||
|
||||
@@ -38,9 +38,9 @@ export const CLIENT_FIREFOX = "firefox";
|
||||
export const CLIENT_USERSCRIPT = "userscript";
|
||||
export const CLIENT_EXTS = [CLIENT_CHROME, CLIENT_EDGE, CLIENT_FIREFOX];
|
||||
|
||||
export const KV_RULES_KEY = "KT_RULES";
|
||||
export const KV_RULES_SHARE_KEY = "KT_RULES_SHARE";
|
||||
export const KV_SETTING_KEY = "KT_SETTING";
|
||||
export const KV_RULES_KEY = "kiss-rules.json";
|
||||
export const KV_RULES_SHARE_KEY = "kiss-rules-share.json";
|
||||
export const KV_SETTING_KEY = "kiss-setting.json";
|
||||
export const KV_SALT_SYNC = "KISS-Translator-SYNC";
|
||||
export const KV_SALT_SHARE = "KISS-Translator-SHARE";
|
||||
|
||||
@@ -293,13 +293,20 @@ export const DEFAULT_SETTING = {
|
||||
|
||||
export const DEFAULT_RULES = [GLOBLA_RULE];
|
||||
|
||||
export const OPT_SYNCTYPE_WORKER = "KISS-Worker";
|
||||
export const OPT_SYNCTYPE_WEBDAV = "WebDAV";
|
||||
export const OPT_SYNCTYPE_ALL = [OPT_SYNCTYPE_WORKER, OPT_SYNCTYPE_WEBDAV];
|
||||
|
||||
export const DEFAULT_SYNC = {
|
||||
syncType: OPT_SYNCTYPE_WORKER, // 同步方式
|
||||
syncUrl: "", // 数据同步接口
|
||||
syncUser: "", // 数据同步用户名
|
||||
syncKey: "", // 数据同步密钥
|
||||
settingUpdateAt: 0,
|
||||
settingSyncAt: 0,
|
||||
rulesUpdateAt: 0,
|
||||
rulesSyncAt: 0,
|
||||
syncMeta: {}, // 数据更新及同步信息
|
||||
// settingUpdateAt: 0,
|
||||
// settingSyncAt: 0,
|
||||
// rulesUpdateAt: 0,
|
||||
// rulesSyncAt: 0,
|
||||
subRulesSyncAt: 0, // 订阅规则同步时间
|
||||
dataCaches: {}, // 缓存同步时间
|
||||
};
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
} from "./config";
|
||||
import { getSettingWithDefault, getRulesWithDefault } from "./libs/storage";
|
||||
import { Translator } from "./libs/translator";
|
||||
import { isIframe } from "./libs/iframe";
|
||||
import { isIframe, sendIframeMsg, sendPrentMsg } from "./libs/iframe";
|
||||
import { matchRule } from "./libs/rules";
|
||||
import { webfix } from "./libs/webfix";
|
||||
|
||||
@@ -15,8 +15,34 @@ import { webfix } from "./libs/webfix";
|
||||
* 入口函数
|
||||
*/
|
||||
const init = async () => {
|
||||
const href = isIframe ? document.referrer : document.location.href;
|
||||
const setting = await getSettingWithDefault();
|
||||
|
||||
if (isIframe) {
|
||||
let translator;
|
||||
window.addEventListener("message", (e) => {
|
||||
const { action, args } = e.data || {};
|
||||
switch (action) {
|
||||
case MSG_TRANS_TOGGLE:
|
||||
translator?.toggle();
|
||||
break;
|
||||
case MSG_TRANS_TOGGLE_STYLE:
|
||||
translator?.toggleStyle();
|
||||
break;
|
||||
case MSG_TRANS_PUTRULE:
|
||||
if (!translator) {
|
||||
translator = new Translator(args, setting);
|
||||
} else {
|
||||
translator.updateRule(args || {});
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
});
|
||||
sendPrentMsg(MSG_TRANS_GETRULE);
|
||||
return;
|
||||
}
|
||||
|
||||
const href = document.location.href;
|
||||
const rules = await getRulesWithDefault();
|
||||
const rule = await matchRule(rules, href, setting);
|
||||
const translator = new Translator(rule, setting);
|
||||
@@ -27,20 +53,32 @@ const init = async () => {
|
||||
switch (action) {
|
||||
case MSG_TRANS_TOGGLE:
|
||||
translator.toggle();
|
||||
sendIframeMsg(MSG_TRANS_TOGGLE);
|
||||
break;
|
||||
case MSG_TRANS_TOGGLE_STYLE:
|
||||
translator.toggleStyle();
|
||||
sendIframeMsg(MSG_TRANS_TOGGLE_STYLE);
|
||||
break;
|
||||
case MSG_TRANS_GETRULE:
|
||||
break;
|
||||
case MSG_TRANS_PUTRULE:
|
||||
translator.updateRule(args);
|
||||
sendIframeMsg(MSG_TRANS_PUTRULE, args);
|
||||
break;
|
||||
default:
|
||||
return { error: `message action is unavailable: ${action}` };
|
||||
}
|
||||
return { data: translator.rule };
|
||||
});
|
||||
window.addEventListener("message", (e) => {
|
||||
const { action } = e.data || {};
|
||||
switch (action) {
|
||||
case MSG_TRANS_GETRULE:
|
||||
sendIframeMsg(MSG_TRANS_PUTRULE, rule);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
(async () => {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { STOKEY_RULES, DEFAULT_RULES } from "../config";
|
||||
import { STOKEY_RULES, DEFAULT_RULES, KV_RULES_KEY } from "../config";
|
||||
import { useStorage } from "./Storage";
|
||||
import { trySyncRules } from "../libs/sync";
|
||||
import { checkRules } from "../libs/rules";
|
||||
import { useCallback } from "react";
|
||||
import { useSyncMeta } from "./Sync";
|
||||
|
||||
/**
|
||||
* 规则 hook
|
||||
@@ -10,13 +11,15 @@ import { useCallback } from "react";
|
||||
*/
|
||||
export function useRules() {
|
||||
const { data: list, save } = useStorage(STOKEY_RULES, DEFAULT_RULES);
|
||||
const { updateSyncMeta } = useSyncMeta();
|
||||
|
||||
const updateRules = useCallback(
|
||||
async (rules) => {
|
||||
await save(rules);
|
||||
trySyncRules(false, true);
|
||||
await updateSyncMeta(KV_RULES_KEY);
|
||||
trySyncRules();
|
||||
},
|
||||
[save]
|
||||
[save, updateSyncMeta]
|
||||
);
|
||||
|
||||
const add = useCallback(
|
||||
|
||||
@@ -1,25 +1,24 @@
|
||||
import { STOKEY_SETTING, DEFAULT_SETTING } from "../config";
|
||||
import { STOKEY_SETTING, DEFAULT_SETTING, KV_SETTING_KEY } from "../config";
|
||||
import { useStorage } from "./Storage";
|
||||
import { trySyncSetting } from "../libs/sync";
|
||||
import { createContext, useCallback, useContext, useMemo } from "react";
|
||||
import { debounce } from "../libs/utils";
|
||||
import { useSyncMeta } from "./Sync";
|
||||
|
||||
const SettingContext = createContext({
|
||||
setting: {},
|
||||
setting: null,
|
||||
updateSetting: async () => {},
|
||||
reloadSetting: async () => {},
|
||||
});
|
||||
|
||||
export function SettingProvider({ children }) {
|
||||
const { data, update, reload, loading } = useStorage(
|
||||
STOKEY_SETTING,
|
||||
DEFAULT_SETTING
|
||||
);
|
||||
const { data, update, reload } = useStorage(STOKEY_SETTING, DEFAULT_SETTING);
|
||||
const { updateSyncMeta } = useSyncMeta();
|
||||
|
||||
const syncSetting = useMemo(
|
||||
() =>
|
||||
debounce(() => {
|
||||
trySyncSetting(false, true);
|
||||
trySyncSetting();
|
||||
}, [2000]),
|
||||
[]
|
||||
);
|
||||
@@ -27,12 +26,13 @@ export function SettingProvider({ children }) {
|
||||
const updateSetting = useCallback(
|
||||
async (obj) => {
|
||||
await update(obj);
|
||||
await updateSyncMeta(KV_SETTING_KEY);
|
||||
syncSetting();
|
||||
},
|
||||
[update, syncSetting]
|
||||
[update, syncSetting, updateSyncMeta]
|
||||
);
|
||||
|
||||
if (loading) {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { storage } from "../libs/storage";
|
||||
|
||||
export function useStorage(key, defaultVal = null) {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [data, setData] = useState(defaultVal);
|
||||
export function useStorage(key, defaultVal) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [data, setData] = useState(null);
|
||||
|
||||
const save = useCallback(
|
||||
async (val) => {
|
||||
@@ -15,7 +15,7 @@ export function useStorage(key, defaultVal = null) {
|
||||
|
||||
const update = useCallback(
|
||||
async (obj) => {
|
||||
setData((pre) => ({ ...pre, ...obj }));
|
||||
setData((pre = {}) => ({ ...pre, ...obj }));
|
||||
await storage.putObj(key, obj);
|
||||
},
|
||||
[key]
|
||||
@@ -27,27 +27,37 @@ export function useStorage(key, defaultVal = null) {
|
||||
}, [key]);
|
||||
|
||||
const reload = useCallback(async () => {
|
||||
const val = await storage.getObj(key);
|
||||
if (val) {
|
||||
setData(val);
|
||||
} else if (defaultVal) {
|
||||
setData(defaultVal);
|
||||
await storage.setObj(key, defaultVal);
|
||||
try {
|
||||
setLoading(true);
|
||||
const val = await storage.getObj(key);
|
||||
if (val) {
|
||||
setData(val);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("[storage reload]", err.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [key, defaultVal]);
|
||||
}, [key]);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
await reload();
|
||||
const val = await storage.getObj(key);
|
||||
if (val) {
|
||||
setData(val);
|
||||
} else if (defaultVal) {
|
||||
setData(defaultVal);
|
||||
await storage.setObj(key, defaultVal);
|
||||
}
|
||||
} catch (err) {
|
||||
//
|
||||
console.log("[storage load]", err.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
})();
|
||||
}, [reload]);
|
||||
}, [key, defaultVal]);
|
||||
|
||||
return { data, save, update, remove, reload, loading };
|
||||
}
|
||||
|
||||
@@ -11,6 +11,23 @@ export function useSync() {
|
||||
return { sync: data, updateSync: update, reloadSync: reload };
|
||||
}
|
||||
|
||||
/**
|
||||
* update syncmeta hook
|
||||
* @returns
|
||||
*/
|
||||
export function useSyncMeta() {
|
||||
const { sync, updateSync } = useSync();
|
||||
const updateSyncMeta = useCallback(
|
||||
async (key) => {
|
||||
const syncMeta = sync?.syncMeta || {};
|
||||
syncMeta[key] = { ...(syncMeta[key] || {}), updateAt: Date.now() };
|
||||
await updateSync({ syncMeta });
|
||||
},
|
||||
[sync, updateSync]
|
||||
);
|
||||
return { updateSyncMeta };
|
||||
}
|
||||
|
||||
/**
|
||||
* caches sync hook
|
||||
* @param {*} url
|
||||
@@ -21,7 +38,7 @@ export function useSyncCaches() {
|
||||
|
||||
const updateDataCache = useCallback(
|
||||
async (url) => {
|
||||
const dataCaches = sync.dataCaches || {};
|
||||
const dataCaches = sync?.dataCaches || {};
|
||||
dataCaches[url] = Date.now();
|
||||
await updateSync({ dataCaches });
|
||||
},
|
||||
@@ -30,7 +47,7 @@ export function useSyncCaches() {
|
||||
|
||||
const deleteDataCache = useCallback(
|
||||
async (url) => {
|
||||
const dataCaches = sync.dataCaches || {};
|
||||
const dataCaches = sync?.dataCaches || {};
|
||||
delete dataCaches[url];
|
||||
await updateSync({ dataCaches });
|
||||
},
|
||||
@@ -38,7 +55,7 @@ export function useSyncCaches() {
|
||||
);
|
||||
|
||||
return {
|
||||
dataCaches: sync.dataCaches || {},
|
||||
dataCaches: sync?.dataCaches || {},
|
||||
updateDataCache,
|
||||
deleteDataCache,
|
||||
reloadSync,
|
||||
|
||||
@@ -13,3 +13,5 @@ function _browser() {
|
||||
}
|
||||
|
||||
export const browser = _browser();
|
||||
|
||||
export const isBg = () => globalThis?.ContextType === "BACKGROUND";
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
DEFAULT_FETCH_LIMIT,
|
||||
} from "../config";
|
||||
import { msAuth } from "./auth";
|
||||
import { isBg } from "./browser";
|
||||
|
||||
/**
|
||||
* 油猴脚本的请求封装
|
||||
@@ -28,7 +29,7 @@ export const fetchGM = async (input, { method = "GET", headers, body } = {}) =>
|
||||
headers,
|
||||
data: body,
|
||||
onload: (response) => {
|
||||
if (response.status === 200) {
|
||||
if (response.status < 300) {
|
||||
const headers = new Headers();
|
||||
response.responseHeaders.split("\n").forEach((line) => {
|
||||
const [name, value] = line.split(":").map((item) => item.trim());
|
||||
@@ -66,7 +67,7 @@ const newCacheReq = async (request) => {
|
||||
* @param {*} param0
|
||||
* @returns
|
||||
*/
|
||||
const fetchApi = async ({ input, init = {}, translator, token }) => {
|
||||
export const fetchApi = async ({ input, init = {}, translator, token }) => {
|
||||
if (token) {
|
||||
if (translator === OPT_TRANS_DEEPL) {
|
||||
init.headers["Authorization"] = `DeepL-Auth-Key ${token}`; // DeepL
|
||||
@@ -176,13 +177,13 @@ export const fetchData = async (
|
||||
* @param {*} opts
|
||||
* @returns
|
||||
*/
|
||||
export const fetchPolyfill = async (input, { isBg = false, ...opts } = {}) => {
|
||||
export const fetchPolyfill = async (input, opts) => {
|
||||
if (!input.trim()) {
|
||||
throw new Error("URL is empty");
|
||||
}
|
||||
|
||||
// 插件
|
||||
if (isExt && !isBg) {
|
||||
if (isExt && !isBg()) {
|
||||
const res = await sendBgMsg(MSG_FETCH, { input, opts });
|
||||
if (res.error) {
|
||||
throw new Error(res.error);
|
||||
|
||||
@@ -5,3 +5,7 @@ export const sendIframeMsg = (action, args) => {
|
||||
iframe.contentWindow.postMessage({ action, args }, "*");
|
||||
});
|
||||
};
|
||||
|
||||
export const sendPrentMsg = (action, args) => {
|
||||
window.parent.postMessage({ action, args }, "*");
|
||||
};
|
||||
|
||||
@@ -149,5 +149,5 @@ export const saveRule = async (newRule) => {
|
||||
rules.unshift(newRule);
|
||||
}
|
||||
await setRules(rules);
|
||||
trySyncRules(false, true);
|
||||
trySyncRules();
|
||||
};
|
||||
|
||||
@@ -25,8 +25,8 @@ const updateSyncDataCache = async (url) => {
|
||||
* @param {*} url
|
||||
* @returns
|
||||
*/
|
||||
export const syncSubRules = async (url, isBg = false) => {
|
||||
const res = await apiFetch(url, isBg);
|
||||
export const syncSubRules = async (url) => {
|
||||
const res = await apiFetch(url);
|
||||
const rules = checkRules(res).filter(
|
||||
({ pattern }) => !isAllchar(pattern, GLOBAL_KEY)
|
||||
);
|
||||
@@ -41,10 +41,10 @@ export const syncSubRules = async (url, isBg = false) => {
|
||||
* @param {*} url
|
||||
* @returns
|
||||
*/
|
||||
export const syncAllSubRules = async (subrulesList, isBg = false) => {
|
||||
export const syncAllSubRules = async (subrulesList) => {
|
||||
for (let subrules of subrulesList) {
|
||||
try {
|
||||
await syncSubRules(subrules.url, isBg);
|
||||
await syncSubRules(subrules.url);
|
||||
await updateSyncDataCache(subrules.url);
|
||||
} catch (err) {
|
||||
console.log(`[sync subrule error]: ${subrules.url}`, err);
|
||||
@@ -57,14 +57,14 @@ export const syncAllSubRules = async (subrulesList, isBg = false) => {
|
||||
* @param {*} url
|
||||
* @returns
|
||||
*/
|
||||
export const trySyncAllSubRules = async ({ subrulesList }, isBg = false) => {
|
||||
export const trySyncAllSubRules = async ({ subrulesList }) => {
|
||||
try {
|
||||
const { subRulesSyncAt } = await getSyncWithDefault();
|
||||
const now = Date.now();
|
||||
const interval = 24 * 60 * 60 * 1000; // 间隔一天
|
||||
if (now - subRulesSyncAt > interval) {
|
||||
// 同步订阅规则
|
||||
await syncAllSubRules(subrulesList, isBg);
|
||||
await syncAllSubRules(subrulesList);
|
||||
await updateSync({ subRulesSyncAt: now });
|
||||
|
||||
// 同步修复规则
|
||||
|
||||
189
src/libs/sync.js
189
src/libs/sync.js
@@ -1,8 +1,10 @@
|
||||
import {
|
||||
APP_LCNAME,
|
||||
KV_SETTING_KEY,
|
||||
KV_RULES_KEY,
|
||||
KV_RULES_SHARE_KEY,
|
||||
KV_SALT_SHARE,
|
||||
OPT_SYNCTYPE_WEBDAV,
|
||||
} from "../config";
|
||||
import {
|
||||
getSyncWithDefault,
|
||||
@@ -13,53 +15,102 @@ import {
|
||||
setRules,
|
||||
} from "./storage";
|
||||
import { apiSyncData } from "../apis";
|
||||
import { sha256 } from "./utils";
|
||||
import { sha256, removeEndchar } from "./utils";
|
||||
import { createClient, getPatcher } from "webdav";
|
||||
import { fetchApi } from "./fetch";
|
||||
|
||||
getPatcher().patch("request", (opts) => {
|
||||
return fetchApi({
|
||||
input: opts.url,
|
||||
init: { method: opts.method, headers: opts.headers, body: opts.data },
|
||||
});
|
||||
});
|
||||
|
||||
const syncByWebdav = async (data, { syncUrl, syncUser, syncKey }) => {
|
||||
const client = createClient(syncUrl, {
|
||||
username: syncUser,
|
||||
password: syncKey,
|
||||
});
|
||||
const pathname = `/${APP_LCNAME}`;
|
||||
const filename = `/${APP_LCNAME}/${data.key}`;
|
||||
|
||||
if ((await client.exists(pathname)) === false) {
|
||||
await client.createDirectory(pathname);
|
||||
}
|
||||
|
||||
const isExist = await client.exists(filename);
|
||||
if (isExist) {
|
||||
const cont = await client.getFileContents(filename, { format: "text" });
|
||||
const webData = JSON.parse(cont);
|
||||
if (webData.updateAt >= data.updateAt) {
|
||||
return webData;
|
||||
}
|
||||
}
|
||||
|
||||
await client.putFileContents(filename, JSON.stringify(data, null, 2));
|
||||
return data;
|
||||
};
|
||||
|
||||
const syncByWorker = async (data, { syncUrl, syncKey }) => {
|
||||
syncUrl = removeEndchar(syncUrl, "/");
|
||||
return await apiSyncData(`${syncUrl}/sync`, syncKey, data);
|
||||
};
|
||||
|
||||
const syncData = async (key, valueFn) => {
|
||||
const {
|
||||
syncType,
|
||||
syncUrl,
|
||||
syncUser,
|
||||
syncKey,
|
||||
syncMeta = {},
|
||||
} = await getSyncWithDefault();
|
||||
if (!syncUrl || !syncKey || (syncType === OPT_SYNCTYPE_WEBDAV && !syncUser)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let { updateAt = 0, syncAt = 0 } = syncMeta[key] || {};
|
||||
syncAt === 0 && (updateAt = 0);
|
||||
|
||||
const value = await valueFn();
|
||||
const data = {
|
||||
key,
|
||||
value: JSON.stringify(value),
|
||||
updateAt,
|
||||
};
|
||||
const args = {
|
||||
syncUrl,
|
||||
syncUser,
|
||||
syncKey,
|
||||
};
|
||||
|
||||
const res =
|
||||
syncType === OPT_SYNCTYPE_WEBDAV
|
||||
? await syncByWebdav(data, args)
|
||||
: await syncByWorker(data, args);
|
||||
|
||||
syncMeta[key] = {
|
||||
updateAt: res.updateAt,
|
||||
syncAt: Date.now(),
|
||||
};
|
||||
await updateSync({ syncMeta });
|
||||
|
||||
return { value: JSON.parse(res.value), isNew: res.updateAt > updateAt };
|
||||
};
|
||||
|
||||
/**
|
||||
* 同步设置
|
||||
* @returns
|
||||
*/
|
||||
const syncSetting = async (isBg = false, isForce = false) => {
|
||||
let {
|
||||
syncUrl,
|
||||
syncKey,
|
||||
settingUpdateAt = 0,
|
||||
settingSyncAt = 0,
|
||||
} = await getSyncWithDefault();
|
||||
if (!syncUrl || !syncKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isForce) {
|
||||
settingUpdateAt = Date.now();
|
||||
}
|
||||
|
||||
const setting = await getSettingWithDefault();
|
||||
const res = await apiSyncData(
|
||||
syncUrl,
|
||||
syncKey,
|
||||
{
|
||||
key: KV_SETTING_KEY,
|
||||
value: setting,
|
||||
updateAt: settingSyncAt === 0 ? 0 : settingUpdateAt,
|
||||
},
|
||||
isBg
|
||||
);
|
||||
|
||||
if (res.updateAt > settingUpdateAt) {
|
||||
const syncSetting = async () => {
|
||||
const res = await syncData(KV_SETTING_KEY, getSettingWithDefault);
|
||||
if (res?.isNew) {
|
||||
await setSetting(res.value);
|
||||
}
|
||||
await updateSync({
|
||||
settingUpdateAt: res.updateAt,
|
||||
settingSyncAt: Date.now(),
|
||||
});
|
||||
|
||||
return res.value;
|
||||
};
|
||||
|
||||
export const trySyncSetting = async (isBg = false, isForce = false) => {
|
||||
export const trySyncSetting = async () => {
|
||||
try {
|
||||
return await syncSetting(isBg, isForce);
|
||||
await syncSetting();
|
||||
} catch (err) {
|
||||
console.log("[sync setting]", err);
|
||||
}
|
||||
@@ -69,47 +120,16 @@ export const trySyncSetting = async (isBg = false, isForce = false) => {
|
||||
* 同步规则
|
||||
* @returns
|
||||
*/
|
||||
const syncRules = async (isBg = false, isForce = false) => {
|
||||
let {
|
||||
syncUrl,
|
||||
syncKey,
|
||||
rulesUpdateAt = 0,
|
||||
rulesSyncAt = 0,
|
||||
} = await getSyncWithDefault();
|
||||
if (!syncUrl || !syncKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isForce) {
|
||||
rulesUpdateAt = Date.now();
|
||||
}
|
||||
|
||||
const rules = await getRulesWithDefault();
|
||||
const res = await apiSyncData(
|
||||
syncUrl,
|
||||
syncKey,
|
||||
{
|
||||
key: KV_RULES_KEY,
|
||||
value: rules,
|
||||
updateAt: rulesSyncAt === 0 ? 0 : rulesUpdateAt,
|
||||
},
|
||||
isBg
|
||||
);
|
||||
|
||||
if (res.updateAt > rulesUpdateAt) {
|
||||
const syncRules = async () => {
|
||||
const res = await syncData(KV_RULES_KEY, getRulesWithDefault);
|
||||
if (res?.isNew) {
|
||||
await setRules(res.value);
|
||||
}
|
||||
await updateSync({
|
||||
rulesUpdateAt: res.updateAt,
|
||||
rulesSyncAt: Date.now(),
|
||||
});
|
||||
|
||||
return res.value;
|
||||
};
|
||||
|
||||
export const trySyncRules = async (isBg = false, isForce = false) => {
|
||||
export const trySyncRules = async () => {
|
||||
try {
|
||||
return await syncRules(isBg, isForce);
|
||||
await syncRules();
|
||||
} catch (err) {
|
||||
console.log("[sync user rules]", err);
|
||||
}
|
||||
@@ -121,13 +141,18 @@ export const trySyncRules = async (isBg = false, isForce = false) => {
|
||||
* @returns
|
||||
*/
|
||||
export const syncShareRules = async ({ rules, syncUrl, syncKey }) => {
|
||||
await apiSyncData(syncUrl, syncKey, {
|
||||
const data = {
|
||||
key: KV_RULES_SHARE_KEY,
|
||||
value: rules,
|
||||
value: JSON.stringify(rules, null, 2),
|
||||
updateAt: Date.now(),
|
||||
});
|
||||
};
|
||||
const args = {
|
||||
syncUrl,
|
||||
syncKey,
|
||||
};
|
||||
await syncByWorker(data, args);
|
||||
const psk = await sha256(syncKey, KV_SALT_SHARE);
|
||||
const shareUrl = `${syncUrl}?psk=${psk}`;
|
||||
const shareUrl = `${syncUrl}/rules?psk=${psk}`;
|
||||
return shareUrl;
|
||||
};
|
||||
|
||||
@@ -135,10 +160,12 @@ export const syncShareRules = async ({ rules, syncUrl, syncKey }) => {
|
||||
* 同步个人设置和规则
|
||||
* @returns
|
||||
*/
|
||||
export const syncSettingAndRules = async (isBg = false) => {
|
||||
return [await syncSetting(isBg), await syncRules(isBg)];
|
||||
export const syncSettingAndRules = async () => {
|
||||
await syncSetting();
|
||||
await syncRules();
|
||||
};
|
||||
|
||||
export const trySyncSettingAndRules = async (isBg = false) => {
|
||||
return [await trySyncSetting(isBg), await trySyncRules(isBg)];
|
||||
export const trySyncSettingAndRules = async () => {
|
||||
await trySyncSetting();
|
||||
await trySyncRules();
|
||||
};
|
||||
|
||||
@@ -10,8 +10,13 @@ import {
|
||||
} from "./libs/storage";
|
||||
import { Translator } from "./libs/translator";
|
||||
import { trySyncAllSubRules } from "./libs/subRules";
|
||||
import { MSG_TRANS_TOGGLE, MSG_TRANS_PUTRULE } from "./config";
|
||||
import { isIframe } from "./libs/iframe";
|
||||
import {
|
||||
MSG_TRANS_TOGGLE,
|
||||
MSG_TRANS_TOGGLE_STYLE,
|
||||
MSG_TRANS_GETRULE,
|
||||
MSG_TRANS_PUTRULE,
|
||||
} from "./config";
|
||||
import { isIframe, sendIframeMsg, sendPrentMsg } from "./libs/iframe";
|
||||
import { handlePing, injectScript } from "./libs/gm";
|
||||
import { matchRule } from "./libs/rules";
|
||||
import { genEventName } from "./libs/utils";
|
||||
@@ -46,29 +51,49 @@ const init = async () => {
|
||||
}
|
||||
|
||||
// 翻译页面
|
||||
const href = isIframe ? document.referrer : document.location.href;
|
||||
const setting = await getSettingWithDefault();
|
||||
|
||||
if (isIframe) {
|
||||
let translator;
|
||||
window.addEventListener("message", (e) => {
|
||||
const { action, args } = e.data || {};
|
||||
switch (action) {
|
||||
case MSG_TRANS_TOGGLE:
|
||||
translator?.toggle();
|
||||
break;
|
||||
case MSG_TRANS_TOGGLE_STYLE:
|
||||
translator?.toggleStyle();
|
||||
break;
|
||||
case MSG_TRANS_PUTRULE:
|
||||
if (!translator) {
|
||||
translator = new Translator(args, setting);
|
||||
} else {
|
||||
translator.updateRule(args || {});
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
});
|
||||
sendPrentMsg(MSG_TRANS_GETRULE);
|
||||
return;
|
||||
}
|
||||
|
||||
const href = isIframe ? document.referrer : document.location.href;
|
||||
const rules = await getRulesWithDefault();
|
||||
const rule = await matchRule(rules, href, setting);
|
||||
const translator = new Translator(rule, setting);
|
||||
webfix(href, setting);
|
||||
|
||||
if (isIframe) {
|
||||
// iframe
|
||||
window.addEventListener("message", (e) => {
|
||||
const action = e?.data?.action;
|
||||
switch (action) {
|
||||
case MSG_TRANS_TOGGLE:
|
||||
translator.toggle();
|
||||
break;
|
||||
case MSG_TRANS_PUTRULE:
|
||||
translator.updateRule(e.data.args || {});
|
||||
break;
|
||||
default:
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 监听消息
|
||||
window.addEventListener("message", (e) => {
|
||||
const { action } = e.data || {};
|
||||
switch (action) {
|
||||
case MSG_TRANS_GETRULE:
|
||||
sendIframeMsg(MSG_TRANS_PUTRULE, rule);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
});
|
||||
|
||||
// 浮球按钮
|
||||
const fab = await getFabWithDefault();
|
||||
|
||||
@@ -16,8 +16,11 @@ import {
|
||||
OPT_SHORTCUT_STYLE,
|
||||
OPT_SHORTCUT_POPUP,
|
||||
OPT_SHORTCUT_SETTING,
|
||||
MSG_TRANS_TOGGLE,
|
||||
MSG_TRANS_TOGGLE_STYLE,
|
||||
} from "../../config";
|
||||
import { shortcutRegister } from "../../libs/shortcut";
|
||||
import { sendIframeMsg } from "../../libs/iframe";
|
||||
|
||||
export default function Action({ translator, fab }) {
|
||||
const fabWidth = 40;
|
||||
@@ -57,10 +60,12 @@ export default function Action({ translator, fab }) {
|
||||
const clearShortcuts = [
|
||||
shortcutRegister(shortcuts[OPT_SHORTCUT_TRANSLATE], () => {
|
||||
translator.toggle();
|
||||
sendIframeMsg(MSG_TRANS_TOGGLE);
|
||||
setShowPopup(false);
|
||||
}),
|
||||
shortcutRegister(shortcuts[OPT_SHORTCUT_STYLE], () => {
|
||||
translator.toggleStyle();
|
||||
sendIframeMsg(MSG_TRANS_TOGGLE_STYLE);
|
||||
setShowPopup(false);
|
||||
}),
|
||||
shortcutRegister(shortcuts[OPT_SHORTCUT_POPUP], () => {
|
||||
@@ -91,6 +96,7 @@ export default function Action({ translator, fab }) {
|
||||
"Toggle Translate (Alt+q)",
|
||||
(event) => {
|
||||
translator.toggle();
|
||||
sendIframeMsg(MSG_TRANS_TOGGLE);
|
||||
setShowPopup(false);
|
||||
},
|
||||
"Q"
|
||||
@@ -99,6 +105,7 @@ export default function Action({ translator, fab }) {
|
||||
"Toggle Style (Alt+c)",
|
||||
(event) => {
|
||||
translator.toggleStyle();
|
||||
sendIframeMsg(MSG_TRANS_TOGGLE_STYLE);
|
||||
setShowPopup(false);
|
||||
},
|
||||
"C"
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import PropTypes from "prop-types";
|
||||
import AppBar from "@mui/material/AppBar";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import MenuIcon from "@mui/icons-material/Menu";
|
||||
@@ -45,8 +44,4 @@ function Header(props) {
|
||||
);
|
||||
}
|
||||
|
||||
Header.propTypes = {
|
||||
onDrawerToggle: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default Header;
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
OPT_STYLE_DIY,
|
||||
OPT_STYLE_USE_COLOR,
|
||||
URL_KISS_RULES_NEW_ISSUE,
|
||||
OPT_SYNCTYPE_WORKER,
|
||||
} from "../../config";
|
||||
import { useState, useRef, useEffect, useMemo } from "react";
|
||||
import { useI18n } from "../../hooks/I18n";
|
||||
@@ -445,8 +446,8 @@ function ShareButton({ rules, injectRules, selectedUrl }) {
|
||||
const i18n = useI18n();
|
||||
const handleClick = async () => {
|
||||
try {
|
||||
const { syncUrl, syncKey } = await getSyncWithDefault();
|
||||
if (!syncUrl || !syncKey) {
|
||||
const { syncType, syncUrl, syncKey } = await getSyncWithDefault();
|
||||
if (syncType !== OPT_SYNCTYPE_WORKER || !syncUrl || !syncKey) {
|
||||
alert.warning(i18n("error_sync_setting"));
|
||||
return;
|
||||
}
|
||||
@@ -526,6 +527,10 @@ function UserRules({ subRules }) {
|
||||
}
|
||||
}, [showAdd]);
|
||||
|
||||
if (!rules.list) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack spacing={3}>
|
||||
<Stack
|
||||
|
||||
@@ -5,7 +5,13 @@ import { useI18n } from "../../hooks/I18n";
|
||||
import { useSync } from "../../hooks/Sync";
|
||||
import Alert from "@mui/material/Alert";
|
||||
import Link from "@mui/material/Link";
|
||||
import { URL_KISS_WORKER } from "../../config";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import {
|
||||
URL_KISS_WORKER,
|
||||
OPT_SYNCTYPE_ALL,
|
||||
OPT_SYNCTYPE_WORKER,
|
||||
OPT_SYNCTYPE_WEBDAV,
|
||||
} from "../../config";
|
||||
import { useState } from "react";
|
||||
import { syncSettingAndRules } from "../../libs/sync";
|
||||
import Button from "@mui/material/Button";
|
||||
@@ -44,13 +50,37 @@ export default function SyncSetting() {
|
||||
}
|
||||
};
|
||||
|
||||
const { syncUrl = "", syncKey = "" } = sync;
|
||||
if (!sync) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
syncType = OPT_SYNCTYPE_WORKER,
|
||||
syncUrl = "",
|
||||
syncUser = "",
|
||||
syncKey = "",
|
||||
} = sync;
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Stack spacing={3}>
|
||||
<Alert severity="warning">{i18n("sync_warn")}</Alert>
|
||||
|
||||
<TextField
|
||||
select
|
||||
size="small"
|
||||
name="syncType"
|
||||
value={syncType}
|
||||
label={i18n("data_sync_type")}
|
||||
onChange={handleChange}
|
||||
>
|
||||
{OPT_SYNCTYPE_ALL.map((item) => (
|
||||
<MenuItem key={item} value={item}>
|
||||
{item}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
|
||||
<TextField
|
||||
size="small"
|
||||
label={i18n("data_sync_url")}
|
||||
@@ -58,12 +88,24 @@ export default function SyncSetting() {
|
||||
value={syncUrl}
|
||||
onChange={handleChange}
|
||||
helperText={
|
||||
<Link href={URL_KISS_WORKER} target="_blank">
|
||||
{i18n("about_sync_api")}
|
||||
</Link>
|
||||
syncType === OPT_SYNCTYPE_WORKER && (
|
||||
<Link href={URL_KISS_WORKER} target="_blank">
|
||||
{i18n("about_sync_api")}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
{syncType === OPT_SYNCTYPE_WEBDAV && (
|
||||
<TextField
|
||||
size="small"
|
||||
label={i18n("data_sync_user")}
|
||||
name="syncUser"
|
||||
value={syncUser}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
)}
|
||||
|
||||
<TextField
|
||||
size="small"
|
||||
type="password"
|
||||
|
||||
@@ -21,10 +21,10 @@ import {
|
||||
OPT_LANGS_TO,
|
||||
OPT_STYLE_ALL,
|
||||
OPT_STYLE_USE_COLOR,
|
||||
CACHE_NAME,
|
||||
} from "../../config";
|
||||
import { sendIframeMsg } from "../../libs/iframe";
|
||||
import { saveRule } from "../../libs/rules";
|
||||
import { tryClearCaches } from "../../libs";
|
||||
|
||||
export default function Popup({ setShowPopup, translator: tran }) {
|
||||
const i18n = useI18n();
|
||||
@@ -71,11 +71,7 @@ export default function Popup({ setShowPopup, translator: tran }) {
|
||||
};
|
||||
|
||||
const handleClearCache = () => {
|
||||
try {
|
||||
caches.delete(CACHE_NAME);
|
||||
} catch (err) {
|
||||
console.log("[clear cache]", err);
|
||||
}
|
||||
tryClearCaches();
|
||||
};
|
||||
|
||||
const handleSaveRule = async () => {
|
||||
|
||||
Reference in New Issue
Block a user