diff --git a/src/config/i18n.js b/src/config/i18n.js
index 3ade455..07ae872 100644
--- a/src/config/i18n.js
+++ b/src/config/i18n.js
@@ -375,6 +375,14 @@ export const I18N = {
zh: `选择器`,
en: `Selector`,
},
+ rootSelector: {
+ zh: `根选择器`,
+ en: `Root Selector`,
+ },
+ fixerFunction: {
+ zh: `修复函数`,
+ en: `Fixer Function`,
+ },
import: {
zh: `导入`,
en: `Import`,
diff --git a/src/config/index.js b/src/config/index.js
index 00083aa..79da443 100644
--- a/src/config/index.js
+++ b/src/config/index.js
@@ -23,6 +23,7 @@ export const STOKEY_MSAUTH = `${APP_NAME}_msauth`;
export const STOKEY_BDAUTH = `${APP_NAME}_bdauth`;
export const STOKEY_SETTING = `${APP_NAME}_setting`;
export const STOKEY_RULES = `${APP_NAME}_rules`;
+export const STOKEY_WFRULES = `${APP_NAME}_webfix_rules`;
export const STOKEY_WORDS = `${APP_NAME}_words`;
export const STOKEY_SYNC = `${APP_NAME}_sync`;
export const STOKEY_FAB = `${APP_NAME}_fab`;
@@ -41,6 +42,7 @@ export const CLIENT_USERSCRIPT = "userscript";
export const CLIENT_EXTS = [CLIENT_CHROME, CLIENT_EDGE, CLIENT_FIREFOX];
export const KV_RULES_KEY = "kiss-rules.json";
+export const KV_WFRULES_KEY = "kiss-webfix.json";
export const KV_WORDS_KEY = "kiss-words.json";
export const KV_RULES_SHARE_KEY = "kiss-rules-share.json";
export const KV_SETTING_KEY = "kiss-setting.json";
diff --git a/src/hooks/Storage.js b/src/hooks/Storage.js
index b84da0b..07e6524 100644
--- a/src/hooks/Storage.js
+++ b/src/hooks/Storage.js
@@ -1,6 +1,12 @@
import { useCallback, useEffect, useState } from "react";
import { storage } from "../libs/storage";
+/**
+ *
+ * @param {*} key
+ * @param {*} defaultVal 需为调用hook外的常量
+ * @returns
+ */
export function useStorage(key, defaultVal) {
const [loading, setLoading] = useState(false);
const [data, setData] = useState(null);
diff --git a/src/hooks/WebfixRules.js b/src/hooks/WebfixRules.js
new file mode 100644
index 0000000..cd91784
--- /dev/null
+++ b/src/hooks/WebfixRules.js
@@ -0,0 +1,58 @@
+import { STOKEY_WFRULES, KV_WFRULES_KEY } from "../config";
+import { useStorage } from "./Storage";
+import { trySyncWebfixRules } from "../libs/sync";
+import { useCallback } from "react";
+import { useSyncMeta } from "./Sync";
+
+const DEFAULT_WFRULES = [];
+
+/**
+ * 修复规则 hook
+ * @returns
+ */
+export function useWebfixRules() {
+ const { data: list, save } = useStorage(STOKEY_WFRULES, DEFAULT_WFRULES);
+ const { updateSyncMeta } = useSyncMeta();
+
+ const updateRules = useCallback(
+ async (rules) => {
+ await save(rules);
+ await updateSyncMeta(KV_WFRULES_KEY);
+ trySyncWebfixRules();
+ },
+ [save, updateSyncMeta]
+ );
+
+ const add = useCallback(
+ async (rule) => {
+ const rules = [...list];
+ if (rules.map((item) => item.pattern).includes(rule.pattern)) {
+ return;
+ }
+ rules.unshift(rule);
+ await updateRules(rules);
+ },
+ [list, updateRules]
+ );
+
+ const del = useCallback(
+ async (pattern) => {
+ let rules = [...list];
+ rules = rules.filter((item) => item.pattern !== pattern);
+ await updateRules(rules);
+ },
+ [list, updateRules]
+ );
+
+ const put = useCallback(
+ async (pattern, obj) => {
+ const rules = [...list];
+ const rule = rules.find((r) => r.pattern === pattern);
+ rule && Object.assign(rule, obj);
+ await updateRules(rules);
+ },
+ [list, updateRules]
+ );
+
+ return { list, add, del, put };
+}
diff --git a/src/libs/storage.js b/src/libs/storage.js
index 6802ae3..3e19c6b 100644
--- a/src/libs/storage.js
+++ b/src/libs/storage.js
@@ -1,6 +1,7 @@
import {
STOKEY_SETTING,
STOKEY_RULES,
+ STOKEY_WFRULES,
STOKEY_WORDS,
STOKEY_FAB,
STOKEY_SYNC,
@@ -98,6 +99,14 @@ export const getRulesWithDefault = async () =>
(await getRules()) || DEFAULT_RULES;
export const setRules = (val) => setObj(STOKEY_RULES, val);
+/**
+ * 修复规则列表
+ */
+export const getWebfixRules = () => getObj(STOKEY_WFRULES);
+export const getWebfixRulesWithDefault = async () =>
+ (await getWebfixRules()) || [];
+export const setWebfixRules = (val) => setObj(STOKEY_WFRULES, val);
+
/**
* 词汇列表
*/
diff --git a/src/libs/sync.js b/src/libs/sync.js
index 7d9743a..3e7b34a 100644
--- a/src/libs/sync.js
+++ b/src/libs/sync.js
@@ -2,6 +2,7 @@ import {
APP_LCNAME,
KV_SETTING_KEY,
KV_RULES_KEY,
+ KV_WFRULES_KEY,
KV_WORDS_KEY,
KV_RULES_SHARE_KEY,
KV_SALT_SHARE,
@@ -13,8 +14,10 @@ import {
getSettingWithDefault,
getRulesWithDefault,
getWordsWithDefault,
+ getWebfixRulesWithDefault,
setSetting,
setRules,
+ setWebfixRules,
setWords,
} from "./storage";
import { apiSyncData } from "../apis";
@@ -138,6 +141,25 @@ export const trySyncRules = async () => {
}
};
+/**
+ * 同步修复规则
+ * @returns
+ */
+const syncWebfixRules = async () => {
+ const res = await syncData(KV_WFRULES_KEY, getWebfixRulesWithDefault);
+ if (res?.isNew) {
+ await setWebfixRules(res.value);
+ }
+};
+
+export const trySyncWebfixRules = async () => {
+ try {
+ await syncWebfixRules();
+ } catch (err) {
+ console.log("[sync user webfix rules]", err);
+ }
+};
+
/**
* 同步词汇
* @returns
@@ -185,11 +207,13 @@ export const syncShareRules = async ({ rules, syncUrl, syncKey }) => {
export const syncSettingAndRules = async () => {
await syncSetting();
await syncRules();
+ await syncWebfixRules();
await syncWords();
};
export const trySyncSettingAndRules = async () => {
await trySyncSetting();
await trySyncRules();
+ await trySyncWebfixRules();
await trySyncWords();
};
diff --git a/src/libs/webfix.js b/src/libs/webfix.js
index 2ab3a01..c9a7c85 100644
--- a/src/libs/webfix.js
+++ b/src/libs/webfix.js
@@ -5,9 +5,10 @@ import { apiFetch } from "../apis";
/**
* 修复程序类型
*/
-const FIXER_BR = "br";
+export const FIXER_BR = "br";
const FIXER_BN = "bn";
const FIXER_FONTSIZE = "fontSize";
+export const FIXER_ALL = [FIXER_BR, FIXER_BN, FIXER_FONTSIZE];
/**
* 需要修复的站点列表
diff --git a/src/views/Options/Webfix.js b/src/views/Options/Webfix.js
index b5486a5..a6ca014 100644
--- a/src/views/Options/Webfix.js
+++ b/src/views/Options/Webfix.js
@@ -13,43 +13,209 @@ import FormControlLabel from "@mui/material/FormControlLabel";
import Switch from "@mui/material/Switch";
import { useSetting } from "../../hooks/Setting";
import CircularProgress from "@mui/material/CircularProgress";
-import { syncWebfix, loadOrFetchWebfix } from "../../libs/webfix";
+import {
+ syncWebfix,
+ loadOrFetchWebfix,
+ FIXER_BR,
+ FIXER_ALL,
+} from "../../libs/webfix";
import Button from "@mui/material/Button";
import SyncIcon from "@mui/icons-material/Sync";
import { useAlert } from "../../hooks/Alert";
import HelpButton from "./HelpButton";
import { URL_KISS_RULES_NEW_ISSUE } from "../../config";
+import MenuItem from "@mui/material/MenuItem";
+import { useWebfixRules } from "../../hooks/WebfixRules";
+
+function WebfixFields({ rule, webfix, setShow }) {
+ const editMode = !!rule;
+ const initFormValues = rule || {
+ pattern: "",
+ selector: "",
+ rootSelector: "",
+ fixer: FIXER_BR,
+ };
+ const i18n = useI18n();
+ const [disabled, setDisabled] = useState(editMode);
+ const [errors, setErrors] = useState({});
+ const [formValues, setFormValues] = useState(initFormValues);
+
+ const { pattern, selector, rootSelector, fixer } = formValues;
+
+ const hasSamePattern = (str) => {
+ for (const item of webfix.list || []) {
+ if (item.pattern === str && rule?.pattern !== str) {
+ return true;
+ }
+ }
+ return false;
+ };
+
+ const handleFocus = (e) => {
+ e.preventDefault();
+ const { name } = e.target;
+ setErrors((pre) => ({ ...pre, [name]: "" }));
+ };
+
+ const handleChange = (e) => {
+ e.preventDefault();
+ const { name, value } = e.target;
+ setFormValues((pre) => ({ ...pre, [name]: value }));
+ };
+
+ const handleCancel = (e) => {
+ e.preventDefault();
+ if (editMode) {
+ setDisabled(true);
+ } else {
+ setShow(false);
+ }
+ setFormValues(initFormValues);
+ };
+
+ const handleSubmit = (e) => {
+ e.preventDefault();
+ const errors = {};
+ if (!pattern.trim()) {
+ errors.pattern = i18n("error_cant_be_blank");
+ }
+ if (hasSamePattern(pattern)) {
+ errors.pattern = i18n("error_duplicate_values");
+ }
+ if (!selector.trim()) {
+ errors.selector = i18n("error_cant_be_blank");
+ }
+ if (Object.keys(errors).length > 0) {
+ setErrors(errors);
+ return;
+ }
+
+ if (editMode) {
+ // 编辑
+ setDisabled(true);
+ webfix.put(rule.pattern, formValues);
+ } else {
+ // 添加
+ webfix.add(formValues);
+ setShow(false);
+ setFormValues(initFormValues);
+ }
+ };
-function ApiFields({ site }) {
- const { selector, rootSelector, fixer } = site;
return (
-