import Box from "@mui/material/Box"; import Stack from "@mui/material/Stack"; import TextField from "@mui/material/TextField"; import Button from "@mui/material/Button"; import CircularProgress from "@mui/material/CircularProgress"; import Alert from "@mui/material/Alert"; import { GLOBAL_KEY, DEFAULT_RULE, GLOBLA_RULE, OPT_LANGS_FROM, OPT_LANGS_TO, OPT_TRANS_ALL, OPT_STYLE_ALL, OPT_STYLE_DIY, OPT_STYLE_USE_COLOR, URL_KISS_RULES_NEW_ISSUE, OPT_SYNCTYPE_WORKER, OPT_TIMING_PAGESCROLL, DEFAULT_TRANS_TAG, OPT_TIMING_ALL, } from "../../config"; import { useState, useEffect, useMemo } from "react"; import { useI18n } from "../../hooks/I18n"; import Typography from "@mui/material/Typography"; import Accordion from "@mui/material/Accordion"; import AccordionSummary from "@mui/material/AccordionSummary"; import AccordionDetails from "@mui/material/AccordionDetails"; import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; import { useRules } from "../../hooks/Rules"; import MenuItem from "@mui/material/MenuItem"; import Grid from "@mui/material/Grid"; import { useSetting } from "../../hooks/Setting"; import FormControlLabel from "@mui/material/FormControlLabel"; import Switch from "@mui/material/Switch"; import Tabs from "@mui/material/Tabs"; import Tab from "@mui/material/Tab"; import Radio from "@mui/material/Radio"; import RadioGroup from "@mui/material/RadioGroup"; import DeleteIcon from "@mui/icons-material/Delete"; import IconButton from "@mui/material/IconButton"; import ShareIcon from "@mui/icons-material/Share"; import SyncIcon from "@mui/icons-material/Sync"; import { useSubRules } from "../../hooks/SubRules"; import { syncSubRules } from "../../libs/subRules"; import { loadOrFetchSubRules } from "../../libs/subRules"; import { useAlert } from "../../hooks/Alert"; import { syncShareRules } from "../../libs/sync"; import { debounce } from "../../libs/utils"; import { delSubRules, getSyncWithDefault } from "../../libs/storage"; import OwSubRule from "./OwSubRule"; import ClearAllIcon from "@mui/icons-material/ClearAll"; import HelpButton from "./HelpButton"; import { useSyncCaches } from "../../hooks/Sync"; import DownloadButton from "./DownloadButton"; import UploadButton from "./UploadButton"; import { FIXER_ALL } from "../../libs/webfix"; import AddIcon from "@mui/icons-material/Add"; import EditIcon from "@mui/icons-material/Edit"; import CancelIcon from "@mui/icons-material/Cancel"; import SaveIcon from "@mui/icons-material/Save"; import { kissLog } from "../../libs/log"; function RuleFields({ rule, rules, setShow, setKeyword }) { const initFormValues = { ...(rule?.pattern === "*" ? GLOBLA_RULE : DEFAULT_RULE), ...(rule || {}), }; const editMode = !!rule; const i18n = useI18n(); const [disabled, setDisabled] = useState(editMode); const [errors, setErrors] = useState({}); const [formValues, setFormValues] = useState(initFormValues); const [showMore, setShowMore] = useState(!rules); const { pattern, selector, keepSelector = "", terms = "", selectStyle = "", parentStyle = "", injectJs = "", injectCss = "", translator, fromLang, toLang, textStyle, transOpen, bgColor, textDiyStyle, transOnly = "false", transTiming = OPT_TIMING_PAGESCROLL, transTag = DEFAULT_TRANS_TAG, transTitle = "false", detectRemote = "false", skipLangs = [], fixerSelector = "", fixerFunc = "-", transStartHook = "", transEndHook = "", transRemoveHook = "", } = formValues; const hasSamePattern = (str) => { for (const item of rules.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 handlePatternChange = useMemo( () => debounce(async (patterns) => { setKeyword(patterns.trim()); }, 500), [setKeyword] ); const handleChange = (e) => { e.preventDefault(); const { name, value } = e.target; setFormValues((pre) => ({ ...pre, [name]: value })); if (name === "pattern" && !editMode) { handlePatternChange(value); } }; const handleCancel = (e) => { e.preventDefault(); if (editMode) { setDisabled(true); } else { setShow(false); } setErrors({}); 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 (pattern === "*" && !errors.pattern && !selector.trim()) { errors.selector = i18n("error_cant_be_blank"); } if (Object.keys(errors).length > 0) { setErrors(errors); return; } if (editMode) { // 编辑 setDisabled(true); rules.put(rule.pattern, formValues); } else { // 添加 rules.add(formValues); setShow(false); setFormValues(initFormValues); } }; const GlobalItem = rule?.pattern !== "*" && ( {GLOBAL_KEY} ); return (
{GlobalItem} {i18n("default_enabled")} {i18n("default_disabled")} {GlobalItem} {OPT_TRANS_ALL.map((item) => ( {item} ))} {GlobalItem} {OPT_LANGS_FROM.map(([lang, name]) => ( {name} ))} {GlobalItem} {OPT_LANGS_TO.map(([lang, name]) => ( {name} ))} {GlobalItem} {OPT_STYLE_ALL.map((item) => ( {i18n(item)} ))} {OPT_STYLE_USE_COLOR.includes(textStyle) && ( )} {textStyle === OPT_STYLE_DIY && ( )} {showMore && ( <> {GlobalItem} {i18n("disable")} {i18n("enable")} {GlobalItem} {OPT_TIMING_ALL.map((item) => ( {i18n(item)} ))} {GlobalItem} {``} {``} {GlobalItem} {i18n("disable")} {i18n("enable")} {GlobalItem} {i18n("disable")} {i18n("enable")} {OPT_LANGS_TO.map(([langKey, langName]) => ( {langName} ))} {GlobalItem} {FIXER_ALL.map((item) => ( {item} ))} )} {rules && (editMode ? ( // 编辑 {disabled ? ( <> {rule?.pattern !== "*" && ( )} {!showMore && ( )} ) : ( <> {!showMore && ( )} )} ) : ( // 添加 {!showMore && ( )} ))} ); } function RuleAccordion({ rule, rules }) { const i18n = useI18n(); const [expanded, setExpanded] = useState(false); const handleChange = (e) => { setExpanded((pre) => !pre); }; return ( }> {rule.pattern === GLOBAL_KEY ? `[${i18n("global_rule")}] ${rule.pattern}` : rule.pattern} {expanded && } ); } function ShareButton({ rules, injectRules, selectedUrl }) { const alert = useAlert(); const i18n = useI18n(); const handleClick = async () => { try { const { syncType, syncUrl, syncKey } = await getSyncWithDefault(); if (syncType !== OPT_SYNCTYPE_WORKER || !syncUrl || !syncKey) { alert.warning(i18n("error_sync_setting")); return; } const shareRules = [...rules.list]; if (injectRules) { const subRules = await loadOrFetchSubRules(selectedUrl); 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")); kissLog(err, "share rules"); } }; return ( ); } function UserRules({ subRules }) { const i18n = useI18n(); const rules = useRules(); const [showAdd, setShowAdd] = useState(false); const { setting, updateSetting } = useSetting(); const [keyword, setKeyword] = useState(""); const injectRules = !!setting?.injectRules; const { selectedUrl, selectedRules } = subRules; const handleImport = async (data) => { try { await rules.merge(JSON.parse(data)); } catch (err) { kissLog(err, "import rules"); } }; const handleInject = () => { updateSetting({ injectRules: !injectRules, }); }; useEffect(() => { if (!showAdd) { setKeyword(""); } }, [showAdd]); if (!rules.list) { return; } return ( JSON.stringify([...rules.list].reverse(), null, 2)} text={i18n("export")} fileName={`kiss-rules_${Date.now()}.json`} /> } label={i18n("inject_rules")} /> {showAdd && ( )} {rules.list .filter( (rule) => rule.pattern.includes(keyword) || keyword.includes(rule.pattern) ) .map((rule) => ( ))} {injectRules && ( {selectedRules .filter( (rule) => rule.pattern.includes(keyword) || keyword.includes(rule.pattern) ) .map((rule) => ( ))} )} ); } function SubRulesItem({ index, url, syncAt, selectedUrl, delSub, setSelectedRules, updateDataCache, deleteDataCache, }) { const [loading, setLoading] = useState(false); const handleDel = async () => { try { await delSub(url); await delSubRules(url); await deleteDataCache(url); } catch (err) { kissLog(err, "del subrules"); } }; const handleSync = async () => { try { setLoading(true); const rules = await syncSubRules(url); if (rules.length > 0 && url === selectedUrl) { setSelectedRules(rules); } await updateDataCache(url); } catch (err) { kissLog(err, "sync sub rules"); } finally { setLoading(false); } }; return ( } sx={{ overflowWrap: "anywhere", }} label={url} /> {syncAt && ( [{new Date(syncAt).toLocaleString()}] )} {loading ? ( ) : ( )} {index !== 0 && selectedUrl !== url && ( )} ); } function SubRulesEdit({ subList, addSub, updateDataCache }) { const i18n = useI18n(); const [inputText, setInputText] = useState(""); const [inputError, setInputError] = useState(""); const [showInput, setShowInput] = useState(false); const [loading, setLoading] = useState(false); const handleCancel = (e) => { e.preventDefault(); setShowInput(false); setInputText(""); setInputError(""); }; const handleSave = async (e) => { e.preventDefault(); const url = inputText.trim(); if (!url) { setInputError(i18n("error_cant_be_blank")); return; } if (subList.find((item) => item.url === url)) { setInputError(i18n("error_duplicate_values")); return; } try { setLoading(true); const rules = await syncSubRules(url); if (rules.length === 0) { throw new Error("empty rules"); } await addSub(url); await updateDataCache(url); setShowInput(false); setInputText(""); } catch (err) { kissLog(err, "fetch rules"); setInputError(i18n("error_fetch_url")); } finally { setLoading(false); } }; const handleInput = (e) => { e.preventDefault(); setInputText(e.target.value); }; const handleFocus = (e) => { e.preventDefault(); setInputError(""); }; return ( <> {showInput && ( <> )} ); } function SubRules({ subRules }) { const { subList, selectSub, addSub, delSub, selectedUrl, selectedRules, setSelectedRules, loading, } = subRules; const { dataCaches, updateDataCache, deleteDataCache, reloadSync } = useSyncCaches(); const handleSelect = (e) => { const url = e.target.value; selectSub(url); }; useEffect(() => { reloadSync(); }, [selectedRules, reloadSync]); return ( {subList.map((item, index) => ( ))} {loading ? (
) : ( selectedRules.map((rule) => ( )) )}
); } export default function Rules() { const i18n = useI18n(); const [activeTab, setActiveTab] = useState(0); const subRules = useSubRules(); const handleTabChange = (e, newValue) => { setActiveTab(newValue); }; return ( {i18n("rules_warn_1")}
{i18n("rules_warn_2")}
{i18n("rules_warn_3")}
); }