share rules
This commit is contained in:
@@ -8,9 +8,10 @@ import {
|
|||||||
OPT_LANGS_SPECIAL,
|
OPT_LANGS_SPECIAL,
|
||||||
PROMPT_PLACE_FROM,
|
PROMPT_PLACE_FROM,
|
||||||
PROMPT_PLACE_TO,
|
PROMPT_PLACE_TO,
|
||||||
KV_HEADER_KEY,
|
KV_SALT_SYNC,
|
||||||
} from "../config";
|
} from "../config";
|
||||||
import { getSetting, detectLang } from "../libs";
|
import { getSetting, detectLang } from "../libs";
|
||||||
|
import { sha256 } from "../libs/utils";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 同步数据
|
* 同步数据
|
||||||
@@ -25,7 +26,7 @@ export const apiSyncData = async (url, key, data) =>
|
|||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
"Content-type": "application/json",
|
"Content-type": "application/json",
|
||||||
[KV_HEADER_KEY]: key,
|
Authorization: `Bearer ${await sha256(key, KV_SALT_SYNC)}`,
|
||||||
},
|
},
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
|
|||||||
@@ -248,4 +248,12 @@ export const I18N = {
|
|||||||
zh: `数据同步密钥`,
|
zh: `数据同步密钥`,
|
||||||
en: `Data Sync Key`,
|
en: `Data Sync Key`,
|
||||||
},
|
},
|
||||||
|
error_got_some_wrong: {
|
||||||
|
zh: "抱歉,出错了!",
|
||||||
|
en: "Sorry, something went wrong!",
|
||||||
|
},
|
||||||
|
error_sync_setting: {
|
||||||
|
zh: "您的同步设置未填写,无法在线分享。",
|
||||||
|
en: "Your sync settings are missing and cannot be shared online.",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -25,9 +25,11 @@ export const CLIENT_FIREFOX = "firefox";
|
|||||||
export const CLIENT_USERSCRIPT = "userscript";
|
export const CLIENT_USERSCRIPT = "userscript";
|
||||||
export const CLIENT_EXTS = [CLIENT_CHROME, CLIENT_EDGE, CLIENT_FIREFOX];
|
export const CLIENT_EXTS = [CLIENT_CHROME, CLIENT_EDGE, CLIENT_FIREFOX];
|
||||||
|
|
||||||
export const KV_HEADER_KEY = "X-KISS-PSK";
|
|
||||||
export const KV_RULES_KEY = "KT_RULES";
|
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_SETTING_KEY = "KT_SETTING";
|
||||||
|
export const KV_SALT_SYNC = "KISS-Translator-SYNC";
|
||||||
|
export const KV_SALT_SHARE = "KISS-Translator-SHARE";
|
||||||
|
|
||||||
export const CACHE_NAME = `${APP_NAME}_cache`;
|
export const CACHE_NAME = `${APP_NAME}_cache`;
|
||||||
|
|
||||||
|
|||||||
60
src/hooks/Alert.js
Normal file
60
src/hooks/Alert.js
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import { createContext, useContext, useState, forwardRef } from "react";
|
||||||
|
import Snackbar from "@mui/material/Snackbar";
|
||||||
|
import MuiAlert from "@mui/material/Alert";
|
||||||
|
|
||||||
|
const Alert = forwardRef(function Alert(props, ref) {
|
||||||
|
return <MuiAlert elevation={6} ref={ref} variant="filled" {...props} />;
|
||||||
|
});
|
||||||
|
|
||||||
|
const AlertContext = createContext(null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 左下角提示,注入context后,方便全局调用
|
||||||
|
* @param {*} param0
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function AlertProvider({ children }) {
|
||||||
|
const vertical = "top";
|
||||||
|
const horizontal = "center";
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [severity, setSeverity] = useState("info");
|
||||||
|
const [message, setMessage] = useState("");
|
||||||
|
|
||||||
|
const error = (msg) => showAlert(msg, "error");
|
||||||
|
const warning = (msg) => showAlert(msg, "warning");
|
||||||
|
const info = (msg) => showAlert(msg, "info");
|
||||||
|
const success = (msg) => showAlert(msg, "success");
|
||||||
|
|
||||||
|
const showAlert = (msg, type) => {
|
||||||
|
setOpen(true);
|
||||||
|
setMessage(msg);
|
||||||
|
setSeverity(type);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = (_, reason) => {
|
||||||
|
if (reason === "clickaway") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AlertContext.Provider value={{ error, warning, info, success }}>
|
||||||
|
{children}
|
||||||
|
<Snackbar
|
||||||
|
open={open}
|
||||||
|
autoHideDuration={3000}
|
||||||
|
onClose={handleClose}
|
||||||
|
anchorOrigin={{ vertical, horizontal }}
|
||||||
|
>
|
||||||
|
<Alert onClose={handleClose} severity={severity} sx={{ width: "100%" }}>
|
||||||
|
{message}
|
||||||
|
</Alert>
|
||||||
|
</Snackbar>
|
||||||
|
</AlertContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useAlert() {
|
||||||
|
return useContext(AlertContext);
|
||||||
|
}
|
||||||
@@ -3,18 +3,22 @@ import {
|
|||||||
DEFAULT_SYNC,
|
DEFAULT_SYNC,
|
||||||
KV_SETTING_KEY,
|
KV_SETTING_KEY,
|
||||||
KV_RULES_KEY,
|
KV_RULES_KEY,
|
||||||
|
KV_RULES_SHARE_KEY,
|
||||||
STOKEY_SETTING,
|
STOKEY_SETTING,
|
||||||
STOKEY_RULES,
|
STOKEY_RULES,
|
||||||
|
KV_SALT_SHARE,
|
||||||
} from "../config";
|
} from "../config";
|
||||||
import storage from "../libs/storage";
|
import storage from "../libs/storage";
|
||||||
import { getSetting, getRules } from ".";
|
import { getSetting, getRules } from ".";
|
||||||
import { apiSyncData } from "../apis";
|
import { apiSyncData } from "../apis";
|
||||||
|
import { sha256 } from "./utils";
|
||||||
|
|
||||||
const loadOpt = async () => (await storage.getObj(STOKEY_SYNC)) || DEFAULT_SYNC;
|
export const loadSyncOpt = async () =>
|
||||||
|
(await storage.getObj(STOKEY_SYNC)) || DEFAULT_SYNC;
|
||||||
|
|
||||||
export const syncSetting = async () => {
|
export const syncSetting = async () => {
|
||||||
try {
|
try {
|
||||||
const { syncUrl, syncKey, settingUpdateAt } = await loadOpt();
|
const { syncUrl, syncKey, settingUpdateAt } = await loadSyncOpt();
|
||||||
if (!syncUrl || !syncKey) {
|
if (!syncUrl || !syncKey) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -44,7 +48,7 @@ export const syncSetting = async () => {
|
|||||||
|
|
||||||
export const syncRules = async () => {
|
export const syncRules = async () => {
|
||||||
try {
|
try {
|
||||||
const { syncUrl, syncKey, rulesUpdateAt } = await loadOpt();
|
const { syncUrl, syncKey, rulesUpdateAt } = await loadSyncOpt();
|
||||||
if (!syncUrl || !syncKey) {
|
if (!syncUrl || !syncKey) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -72,6 +76,17 @@ export const syncRules = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const syncShareRules = async ({ rules, syncUrl, syncKey }) => {
|
||||||
|
await apiSyncData(syncUrl, syncKey, {
|
||||||
|
key: KV_RULES_SHARE_KEY,
|
||||||
|
value: rules,
|
||||||
|
updateAt: Date.now(),
|
||||||
|
});
|
||||||
|
const psk = await sha256(syncKey, KV_SALT_SHARE);
|
||||||
|
const shareUrl = `${syncUrl}?psk=${psk}`;
|
||||||
|
return shareUrl;
|
||||||
|
};
|
||||||
|
|
||||||
export const syncAll = async () => {
|
export const syncAll = async () => {
|
||||||
await syncSetting();
|
await syncSetting();
|
||||||
await syncRules();
|
await syncRules();
|
||||||
|
|||||||
@@ -98,3 +98,16 @@ export const type = (o) => {
|
|||||||
const s = Object.prototype.toString.call(o);
|
const s = Object.prototype.toString.call(o);
|
||||||
return s.match(/\[object (.*?)\]/)[1].toLowerCase();
|
return s.match(/\[object (.*?)\]/)[1].toLowerCase();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sha256
|
||||||
|
* @param {*} text
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const sha256 = async (text, salt) => {
|
||||||
|
const data = new TextEncoder().encode(text + salt);
|
||||||
|
const digest = await crypto.subtle.digest({ name: "SHA-256" }, data);
|
||||||
|
return [...new Uint8Array(digest)]
|
||||||
|
.map((b) => b.toString(16).padStart(2, "0"))
|
||||||
|
.join("");
|
||||||
|
};
|
||||||
|
|||||||
@@ -32,9 +32,12 @@ import Radio from "@mui/material/Radio";
|
|||||||
import RadioGroup from "@mui/material/RadioGroup";
|
import RadioGroup from "@mui/material/RadioGroup";
|
||||||
import DeleteIcon from "@mui/icons-material/Delete";
|
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 SyncIcon from "@mui/icons-material/Sync";
|
import SyncIcon from "@mui/icons-material/Sync";
|
||||||
import { useSubrules } from "../../hooks/Rules";
|
import { useSubrules } from "../../hooks/Rules";
|
||||||
import { rulesCache, tryLoadRules } from "../../libs/rules";
|
import { rulesCache, tryLoadRules } from "../../libs/rules";
|
||||||
|
import { useAlert } from "../../hooks/Alert";
|
||||||
|
import { loadSyncOpt, syncShareRules } from "../../libs/sync";
|
||||||
|
|
||||||
function RuleFields({ rule, rules, setShow }) {
|
function RuleFields({ rule, rules, setShow }) {
|
||||||
const initFormValues = rule || { ...DEFAULT_RULE, transOpen: "true" };
|
const initFormValues = rule || { ...DEFAULT_RULE, transOpen: "true" };
|
||||||
@@ -393,12 +396,56 @@ function UploadButton({ onChange, text }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ShareButton({ rules, injectRules, selectedSub }) {
|
||||||
|
const alert = useAlert();
|
||||||
|
const i18n = useI18n();
|
||||||
|
const handleClick = async () => {
|
||||||
|
try {
|
||||||
|
const { syncUrl, syncKey } = await loadSyncOpt();
|
||||||
|
if (!syncUrl || !syncKey) {
|
||||||
|
alert.warning(i18n("error_sync_setting"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const shareRules = [...rules.list];
|
||||||
|
if (injectRules) {
|
||||||
|
const subRules = await tryLoadRules(selectedSub?.url);
|
||||||
|
shareRules.splice(-1, 0, ...subRules);
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = await syncShareRules({
|
||||||
|
rules: shareRules,
|
||||||
|
syncUrl,
|
||||||
|
syncKey,
|
||||||
|
});
|
||||||
|
|
||||||
|
window.open(url, "_blank");
|
||||||
|
} catch (err) {
|
||||||
|
alert.warning(i18n("error_got_some_wrong"));
|
||||||
|
console.log("[share rules]", err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
variant="outlined"
|
||||||
|
onClick={handleClick}
|
||||||
|
startIcon={<ShareIcon />}
|
||||||
|
>
|
||||||
|
{"分享"}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function UserRules() {
|
function UserRules() {
|
||||||
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 = useSetting();
|
||||||
const updateSetting = useSettingUpdate();
|
const updateSetting = useSettingUpdate();
|
||||||
|
const subrules = useSubrules();
|
||||||
|
const selectedSub = subrules.list.find((item) => item.selected);
|
||||||
|
|
||||||
const injectRules = !!setting?.injectRules;
|
const injectRules = !!setting?.injectRules;
|
||||||
|
|
||||||
@@ -451,6 +498,12 @@ function UserRules() {
|
|||||||
text={i18n("export")}
|
text={i18n("export")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<ShareButton
|
||||||
|
rules={rules}
|
||||||
|
injectRules={injectRules}
|
||||||
|
selectedSub={selectedSub}
|
||||||
|
/>
|
||||||
|
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
control={
|
control={
|
||||||
<Switch
|
<Switch
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import Link from "@mui/material/Link";
|
|||||||
import { URL_KISS_WORKER } from "../../config";
|
import { URL_KISS_WORKER } from "../../config";
|
||||||
import { debounce } from "../../libs/utils";
|
import { debounce } from "../../libs/utils";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
|
import { syncAll } from "../../libs/sync";
|
||||||
|
|
||||||
export default function SyncSetting() {
|
export default function SyncSetting() {
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
@@ -15,13 +16,14 @@ export default function SyncSetting() {
|
|||||||
|
|
||||||
const handleChange = useMemo(
|
const handleChange = useMemo(
|
||||||
() =>
|
() =>
|
||||||
debounce((e) => {
|
debounce(async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const { name, value } = e.target;
|
const { name, value } = e.target;
|
||||||
sync.update({
|
await sync.update({
|
||||||
[name]: value,
|
[name]: value,
|
||||||
});
|
});
|
||||||
}, 500),
|
await syncAll();
|
||||||
|
}, 1000),
|
||||||
[sync]
|
[sync]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { isGm } from "../../libs/browser";
|
|||||||
import { sleep } from "../../libs/utils";
|
import { sleep } from "../../libs/utils";
|
||||||
import CircularProgress from "@mui/material/CircularProgress";
|
import CircularProgress from "@mui/material/CircularProgress";
|
||||||
import { syncAll } from "../../libs/sync";
|
import { syncAll } from "../../libs/sync";
|
||||||
|
import { AlertProvider } from "../../hooks/Alert";
|
||||||
|
|
||||||
export default function Options() {
|
export default function Options() {
|
||||||
const [error, setError] = useState(false);
|
const [error, setError] = useState(false);
|
||||||
@@ -69,6 +70,7 @@ export default function Options() {
|
|||||||
return (
|
return (
|
||||||
<StoragesProvider>
|
<StoragesProvider>
|
||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
|
<AlertProvider>
|
||||||
<HashRouter>
|
<HashRouter>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<Layout />}>
|
<Route path="/" element={<Layout />}>
|
||||||
@@ -79,6 +81,7 @@ export default function Options() {
|
|||||||
</Route>
|
</Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
</HashRouter>
|
</HashRouter>
|
||||||
|
</AlertProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</StoragesProvider>
|
</StoragesProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user