Files
kiss-translator/src/views/Options/Rules.js

1171 lines
32 KiB
JavaScript
Raw Normal View History

2023-07-20 13:45:41 +08:00
import Box from "@mui/material/Box";
import Stack from "@mui/material/Stack";
import TextField from "@mui/material/TextField";
import Button from "@mui/material/Button";
2023-08-20 19:27:29 +08:00
import CircularProgress from "@mui/material/CircularProgress";
2023-08-22 10:35:57 +08:00
import Alert from "@mui/material/Alert";
2023-07-20 13:45:41 +08:00
import {
2023-08-08 13:29:15 +08:00
GLOBAL_KEY,
2023-07-20 13:45:41 +08:00
DEFAULT_RULE,
2024-03-17 11:35:43 +08:00
GLOBLA_RULE,
2023-07-20 13:45:41 +08:00
OPT_LANGS_FROM,
OPT_LANGS_TO,
OPT_TRANS_ALL,
OPT_STYLE_ALL,
2023-09-01 15:57:05 +08:00
OPT_STYLE_DIY,
OPT_STYLE_USE_COLOR,
2023-09-04 23:12:35 +08:00
URL_KISS_RULES_NEW_ISSUE,
2023-09-18 17:36:10 +08:00
OPT_SYNCTYPE_WORKER,
2024-03-16 23:37:27 +08:00
OPT_TIMING_PAGESCROLL,
DEFAULT_TRANS_TAG,
OPT_TIMING_ALL,
2023-07-20 13:45:41 +08:00
} from "../../config";
2023-10-26 23:55:05 +08:00
import { useState, useEffect, useMemo } from "react";
2023-07-20 13:45:41 +08:00
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";
2023-08-31 00:18:57 +08:00
import { useSetting } from "../../hooks/Setting";
2023-08-17 15:55:44 +08:00
import FormControlLabel from "@mui/material/FormControlLabel";
import Switch from "@mui/material/Switch";
2023-08-20 19:27:29 +08:00
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";
2023-08-20 23:30:08 +08:00
import ShareIcon from "@mui/icons-material/Share";
2023-08-20 19:27:29 +08:00
import SyncIcon from "@mui/icons-material/Sync";
2023-08-31 00:18:57 +08:00
import { useSubRules } from "../../hooks/SubRules";
import { syncSubRules } from "../../libs/subRules";
import { loadOrFetchSubRules } from "../../libs/subRules";
2023-08-20 23:30:08 +08:00
import { useAlert } from "../../hooks/Alert";
2023-08-31 00:18:57 +08:00
import { syncShareRules } from "../../libs/sync";
2023-08-22 17:37:42 +08:00
import { debounce } from "../../libs/utils";
2023-08-31 00:18:57 +08:00
import { delSubRules, getSyncWithDefault } from "../../libs/storage";
2023-09-01 22:27:25 +08:00
import OwSubRule from "./OwSubRule";
2023-09-08 15:32:42 +08:00
import ClearAllIcon from "@mui/icons-material/ClearAll";
2023-09-08 21:57:42 +08:00
import HelpButton from "./HelpButton";
2023-09-11 17:56:31 +08:00
import { useSyncCaches } from "../../hooks/Sync";
2023-10-26 23:55:05 +08:00
import DownloadButton from "./DownloadButton";
import UploadButton from "./UploadButton";
2024-03-16 23:37:27 +08:00
import { FIXER_ALL } from "../../libs/webfix";
2024-03-18 10:48:38 +08:00
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";
2024-03-19 18:07:18 +08:00
import { kissLog } from "../../libs/log";
2023-07-20 13:45:41 +08:00
2023-08-22 17:37:42 +08:00
function RuleFields({ rule, rules, setShow, setKeyword }) {
2024-03-17 11:35:43 +08:00
const initFormValues = {
...(rule?.pattern === "*" ? GLOBLA_RULE : DEFAULT_RULE),
...(rule || {}),
};
2023-07-20 13:45:41 +08:00
const editMode = !!rule;
const i18n = useI18n();
const [disabled, setDisabled] = useState(editMode);
const [errors, setErrors] = useState({});
const [formValues, setFormValues] = useState(initFormValues);
2024-03-14 18:06:28 +08:00
const [showMore, setShowMore] = useState(!rules);
2023-07-20 13:45:41 +08:00
const {
pattern,
selector,
2024-01-02 17:55:59 +08:00
keepSelector = "",
2024-01-19 16:02:53 +08:00
terms = "",
2024-03-14 18:06:28 +08:00
selectStyle = "",
parentStyle = "",
injectJs = "",
injectCss = "",
2023-07-20 13:45:41 +08:00
translator,
fromLang,
toLang,
textStyle,
transOpen,
2023-08-08 16:41:47 +08:00
bgColor,
2023-09-01 15:57:05 +08:00
textDiyStyle,
2024-03-16 23:37:27 +08:00
transOnly = "false",
transTiming = OPT_TIMING_PAGESCROLL,
transTag = DEFAULT_TRANS_TAG,
transTitle = "false",
detectRemote = "false",
skipLangs = [],
fixerSelector = "",
fixerFunc = "-",
2024-05-15 11:07:13 +08:00
transStartHook = "",
transEndHook = "",
transRemoveHook = "",
2023-07-20 13:45:41 +08:00
} = formValues;
const hasSamePattern = (str) => {
2023-07-22 09:00:33 +08:00
for (const item of rules.list) {
2023-07-20 13:45:41 +08:00
if (item.pattern === str && rule?.pattern !== str) {
return true;
}
}
return false;
};
const handleFocus = (e) => {
e.preventDefault();
const { name } = e.target;
setErrors((pre) => ({ ...pre, [name]: "" }));
};
2023-08-22 17:37:42 +08:00
const handlePatternChange = useMemo(
() =>
debounce(async (patterns) => {
setKeyword(patterns.trim());
}, 500),
[setKeyword]
);
2023-07-20 13:45:41 +08:00
const handleChange = (e) => {
e.preventDefault();
const { name, value } = e.target;
setFormValues((pre) => ({ ...pre, [name]: value }));
2023-08-22 17:37:42 +08:00
if (name === "pattern" && !editMode) {
handlePatternChange(value);
}
2023-07-20 13:45:41 +08:00
};
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");
}
2023-08-08 15:36:42 +08:00
if (pattern === "*" && !errors.pattern && !selector.trim()) {
errors.selector = i18n("error_cant_be_blank");
}
2023-07-20 13:45:41 +08:00
if (Object.keys(errors).length > 0) {
setErrors(errors);
return;
}
if (editMode) {
// 编辑
setDisabled(true);
2023-07-28 15:34:05 +08:00
rules.put(rule.pattern, formValues);
2023-07-20 13:45:41 +08:00
} else {
// 添加
2023-07-22 09:00:33 +08:00
rules.add(formValues);
2023-07-20 13:45:41 +08:00
setShow(false);
setFormValues(initFormValues);
}
};
2023-09-01 22:27:25 +08:00
const GlobalItem = rule?.pattern !== "*" && (
2023-08-08 13:29:15 +08:00
<MenuItem key={GLOBAL_KEY} value={GLOBAL_KEY}>
{GLOBAL_KEY}
</MenuItem>
);
2023-07-20 13:45:41 +08:00
return (
<form onSubmit={handleSubmit}>
<Stack spacing={2}>
<TextField
size="small"
label={i18n("pattern")}
error={!!errors.pattern}
2023-08-08 11:31:11 +08:00
helperText={errors.pattern || i18n("pattern_helper")}
2023-07-20 13:45:41 +08:00
name="pattern"
value={pattern}
disabled={rule?.pattern === "*" || disabled}
onChange={handleChange}
onFocus={handleFocus}
2023-08-17 13:27:22 +08:00
multiline
2023-07-20 13:45:41 +08:00
/>
<TextField
size="small"
label={i18n("selector")}
error={!!errors.selector}
2023-08-08 11:31:11 +08:00
helperText={errors.selector || i18n("selector_helper")}
2023-07-20 13:45:41 +08:00
name="selector"
value={selector}
disabled={disabled}
onChange={handleChange}
onFocus={handleFocus}
multiline
/>
2024-01-02 17:55:59 +08:00
<TextField
size="small"
label={i18n("keep_selector")}
helperText={i18n("keep_selector_helper")}
name="keepSelector"
value={keepSelector}
disabled={disabled}
onChange={handleChange}
multiline
/>
2023-07-20 13:45:41 +08:00
<Box>
2023-08-08 16:41:47 +08:00
<Grid container spacing={2} columns={12}>
<Grid item xs={12} sm={6} md={3} lg={2}>
2023-07-20 13:45:41 +08:00
<TextField
select
size="small"
fullWidth
name="transOpen"
value={transOpen}
label={i18n("translate_switch")}
disabled={disabled}
onChange={handleChange}
>
2023-09-01 22:27:25 +08:00
{GlobalItem}
2023-08-08 13:29:15 +08:00
<MenuItem value={"true"}>{i18n("default_enabled")}</MenuItem>
<MenuItem value={"false"}>{i18n("default_disabled")}</MenuItem>
2023-07-20 13:45:41 +08:00
</TextField>
</Grid>
2023-08-08 16:41:47 +08:00
<Grid item xs={12} sm={6} md={3} lg={2}>
2023-07-20 13:45:41 +08:00
<TextField
select
size="small"
fullWidth
name="translator"
value={translator}
label={i18n("translate_service")}
disabled={disabled}
onChange={handleChange}
>
2023-09-01 22:27:25 +08:00
{GlobalItem}
2023-07-20 13:45:41 +08:00
{OPT_TRANS_ALL.map((item) => (
2023-08-04 10:08:54 +08:00
<MenuItem key={item} value={item}>
{item}
</MenuItem>
2023-07-20 13:45:41 +08:00
))}
</TextField>
</Grid>
2023-08-08 16:41:47 +08:00
<Grid item xs={12} sm={6} md={3} lg={2}>
2023-07-20 13:45:41 +08:00
<TextField
select
size="small"
fullWidth
name="fromLang"
value={fromLang}
label={i18n("from_lang")}
disabled={disabled}
onChange={handleChange}
>
2023-09-01 22:27:25 +08:00
{GlobalItem}
2023-07-20 13:45:41 +08:00
{OPT_LANGS_FROM.map(([lang, name]) => (
2023-08-08 13:29:15 +08:00
<MenuItem key={lang} value={lang}>
{name}
</MenuItem>
2023-07-20 13:45:41 +08:00
))}
</TextField>
</Grid>
2023-08-08 16:41:47 +08:00
<Grid item xs={12} sm={6} md={3} lg={2}>
2023-07-20 13:45:41 +08:00
<TextField
select
size="small"
fullWidth
name="toLang"
value={toLang}
label={i18n("to_lang")}
disabled={disabled}
onChange={handleChange}
>
2023-09-01 22:27:25 +08:00
{GlobalItem}
2023-07-20 13:45:41 +08:00
{OPT_LANGS_TO.map(([lang, name]) => (
2023-08-08 13:29:15 +08:00
<MenuItem key={lang} value={lang}>
{name}
</MenuItem>
2023-07-20 13:45:41 +08:00
))}
</TextField>
</Grid>
2023-08-08 16:41:47 +08:00
<Grid item xs={12} sm={6} md={3} lg={2}>
2023-07-20 13:45:41 +08:00
<TextField
select
size="small"
fullWidth
name="textStyle"
value={textStyle}
label={i18n("text_style")}
disabled={disabled}
onChange={handleChange}
>
2023-09-01 22:27:25 +08:00
{GlobalItem}
2023-07-20 13:45:41 +08:00
{OPT_STYLE_ALL.map((item) => (
2023-08-08 13:29:15 +08:00
<MenuItem key={item} value={item}>
{i18n(item)}
</MenuItem>
2023-07-20 13:45:41 +08:00
))}
</TextField>
</Grid>
2023-09-01 15:57:05 +08:00
{OPT_STYLE_USE_COLOR.includes(textStyle) && (
<Grid item xs={12} sm={6} md={3} lg={2}>
<TextField
size="small"
fullWidth
name="bgColor"
value={bgColor}
label={i18n("bg_color")}
disabled={disabled}
onChange={handleChange}
/>
</Grid>
)}
2023-07-20 13:45:41 +08:00
</Grid>
</Box>
2024-03-14 18:08:02 +08:00
{textStyle === OPT_STYLE_DIY && (
<TextField
size="small"
label={i18n("diy_style")}
helperText={i18n("diy_style_helper")}
name="textDiyStyle"
value={textDiyStyle}
disabled={disabled}
onChange={handleChange}
2024-03-15 15:47:57 +08:00
maxRows={10}
2024-03-14 18:08:02 +08:00
multiline
/>
)}
2024-03-14 18:06:28 +08:00
{showMore && (
<>
2024-03-16 23:37:27 +08:00
<Box>
<Grid container spacing={2} columns={12}>
<Grid item xs={12} sm={6} md={3} lg={2}>
<TextField
select
size="small"
fullWidth
name="transOnly"
value={transOnly}
label={i18n("show_only_translations")}
disabled={disabled}
onChange={handleChange}
>
{GlobalItem}
<MenuItem value={"false"}>{i18n("disable")}</MenuItem>
<MenuItem value={"true"}>{i18n("enable")}</MenuItem>
</TextField>
</Grid>
<Grid item xs={12} sm={6} md={3} lg={2}>
<TextField
select
size="small"
fullWidth
name="transTiming"
value={transTiming}
2024-04-16 12:47:55 +08:00
label={i18n("trigger_mode")}
2024-03-16 23:37:27 +08:00
disabled={disabled}
onChange={handleChange}
>
{GlobalItem}
{OPT_TIMING_ALL.map((item) => (
<MenuItem key={item} value={item}>
{i18n(item)}
</MenuItem>
))}
</TextField>
</Grid>
<Grid item xs={12} sm={6} md={3} lg={2}>
<TextField
select
size="small"
fullWidth
name="transTag"
value={transTag}
label={i18n("translation_element_tag")}
disabled={disabled}
onChange={handleChange}
>
{GlobalItem}
<MenuItem value={"span"}>{`<span>`}</MenuItem>
<MenuItem value={"font"}>{`<font>`}</MenuItem>
</TextField>
</Grid>
<Grid item xs={12} sm={6} md={3} lg={2}>
<TextField
select
size="small"
fullWidth
name="transTitle"
value={transTitle}
label={i18n("translate_page_title")}
disabled={disabled}
onChange={handleChange}
>
{GlobalItem}
<MenuItem value={"false"}>{i18n("disable")}</MenuItem>
<MenuItem value={"true"}>{i18n("enable")}</MenuItem>
</TextField>
</Grid>
<Grid item xs={12} sm={6} md={3} lg={2}>
<TextField
select
size="small"
fullWidth
name="detectRemote"
value={detectRemote}
label={i18n("detect_lang_remote")}
disabled={disabled}
onChange={handleChange}
>
{GlobalItem}
<MenuItem value={"false"}>{i18n("disable")}</MenuItem>
<MenuItem value={"true"}>{i18n("enable")}</MenuItem>
</TextField>
</Grid>
</Grid>
</Box>
<TextField
select
size="small"
2024-03-19 10:52:32 +08:00
label={i18n("skip_langs")}
helperText={i18n("skip_langs_helper")}
2024-03-16 23:37:27 +08:00
name="skipLangs"
value={skipLangs}
disabled={disabled}
onChange={handleChange}
SelectProps={{
multiple: true,
}}
>
{OPT_LANGS_TO.map(([langKey, langName]) => (
<MenuItem key={langKey} value={langKey}>
{langName}
</MenuItem>
))}
</TextField>
2024-03-19 10:52:32 +08:00
<TextField
size="small"
label={i18n("terms")}
helperText={i18n("terms_helper")}
name="terms"
value={terms}
disabled={disabled}
onChange={handleChange}
multiline
2024-05-15 11:07:13 +08:00
maxRows={10}
2024-03-19 10:52:32 +08:00
/>
2024-03-16 23:37:27 +08:00
<TextField
size="small"
label={i18n("fixer_selector")}
name="fixerSelector"
value={fixerSelector}
disabled={disabled}
onChange={handleChange}
multiline
2024-05-15 11:07:13 +08:00
maxRows={10}
2024-03-16 23:37:27 +08:00
/>
<TextField
select
size="small"
name="fixerFunc"
value={fixerFunc}
label={i18n("fixer_function")}
helperText={i18n("fixer_function_helper")}
disabled={disabled}
onChange={handleChange}
>
{GlobalItem}
{FIXER_ALL.map((item) => (
<MenuItem key={item} value={item}>
{item}
</MenuItem>
))}
</TextField>
2024-05-15 11:07:13 +08:00
<TextField
size="small"
label={i18n("translate_start_hook")}
helperText={i18n("translate_start_hook_helper")}
name="transStartHook"
value={transStartHook}
disabled={disabled}
onChange={handleChange}
multiline
maxRows={10}
/>
<TextField
size="small"
label={i18n("translate_end_hook")}
helperText={i18n("translate_end_hook_helper")}
name="transEndHook"
value={transEndHook}
disabled={disabled}
onChange={handleChange}
multiline
maxRows={10}
/>
<TextField
size="small"
label={i18n("translate_remove_hook")}
helperText={i18n("translate_remove_hook_helper")}
name="transRemoveHook"
value={transRemoveHook}
disabled={disabled}
onChange={handleChange}
multiline
maxRows={10}
/>
2024-03-14 18:06:28 +08:00
<TextField
size="small"
label={i18n("selector_style")}
2024-03-19 10:52:32 +08:00
helperText={i18n("selector_style_helper")}
2024-03-14 18:06:28 +08:00
name="selectStyle"
value={selectStyle}
disabled={disabled}
onChange={handleChange}
2024-03-15 15:47:57 +08:00
maxRows={10}
2024-03-14 18:06:28 +08:00
multiline
/>
<TextField
size="small"
label={i18n("selector_parent_style")}
2024-03-19 10:52:32 +08:00
helperText={i18n("selector_style_helper")}
2024-03-14 18:06:28 +08:00
name="parentStyle"
value={parentStyle}
disabled={disabled}
onChange={handleChange}
2024-03-15 15:47:57 +08:00
maxRows={10}
2024-03-14 18:06:28 +08:00
multiline
/>
<TextField
size="small"
2024-03-15 17:38:24 +08:00
label={i18n("inject_css")}
2024-03-19 10:52:32 +08:00
helperText={i18n("inject_css_helper")}
2024-03-15 17:38:24 +08:00
name="injectCss"
value={injectCss}
2024-03-14 18:06:28 +08:00
disabled={disabled}
onChange={handleChange}
2024-03-15 15:47:57 +08:00
maxRows={10}
2024-03-14 18:06:28 +08:00
multiline
/>
<TextField
size="small"
2024-03-15 17:38:24 +08:00
label={i18n("inject_js")}
helperText={i18n("inject_js_helper")}
name="injectJs"
value={injectJs}
2024-03-14 18:06:28 +08:00
disabled={disabled}
onChange={handleChange}
2024-03-15 15:47:57 +08:00
maxRows={10}
2024-03-14 18:06:28 +08:00
multiline
/>
</>
)}
2023-08-17 15:55:44 +08:00
{rules &&
(editMode ? (
// 编辑
<Stack direction="row" spacing={2}>
{disabled ? (
<>
2023-07-20 13:45:41 +08:00
<Button
size="small"
2023-08-17 15:55:44 +08:00
variant="contained"
2023-07-20 13:45:41 +08:00
onClick={(e) => {
e.preventDefault();
2023-08-17 15:55:44 +08:00
setDisabled(false);
2023-07-20 13:45:41 +08:00
}}
2024-03-18 10:48:38 +08:00
startIcon={<EditIcon />}
2023-07-20 13:45:41 +08:00
>
2023-08-17 15:55:44 +08:00
{i18n("edit")}
2023-07-20 13:45:41 +08:00
</Button>
2023-08-17 15:55:44 +08:00
{rule?.pattern !== "*" && (
<Button
size="small"
variant="outlined"
onClick={(e) => {
e.preventDefault();
rules.del(rule.pattern);
}}
2024-03-18 10:48:38 +08:00
startIcon={<DeleteIcon />}
2023-08-17 15:55:44 +08:00
>
{i18n("delete")}
</Button>
)}
2024-03-14 18:06:28 +08:00
{!showMore && (
<Button
size="small"
variant="text"
onClick={() => {
setShowMore(true);
}}
2024-03-18 10:48:38 +08:00
startIcon={<ExpandMoreIcon />}
2024-03-14 18:06:28 +08:00
>
{i18n("more")}
</Button>
)}
2023-08-17 15:55:44 +08:00
</>
) : (
<>
2024-03-18 10:48:38 +08:00
<Button
size="small"
variant="contained"
type="submit"
startIcon={<SaveIcon />}
>
2023-08-17 15:55:44 +08:00
{i18n("save")}
</Button>
<Button
size="small"
variant="outlined"
onClick={handleCancel}
2024-03-18 10:48:38 +08:00
startIcon={<CancelIcon />}
2023-08-17 15:55:44 +08:00
>
{i18n("cancel")}
</Button>
2024-03-14 18:06:28 +08:00
{!showMore && (
<Button
size="small"
variant="text"
onClick={() => {
setShowMore(true);
}}
2024-03-18 10:48:38 +08:00
startIcon={<ExpandMoreIcon />}
2024-03-14 18:06:28 +08:00
>
{i18n("more")}
</Button>
)}
2023-08-17 15:55:44 +08:00
</>
)}
</Stack>
) : (
// 添加
<Stack direction="row" spacing={2}>
2024-03-18 10:48:38 +08:00
<Button
size="small"
variant="contained"
type="submit"
startIcon={<SaveIcon />}
>
2023-08-17 15:55:44 +08:00
{i18n("save")}
</Button>
2024-03-18 10:48:38 +08:00
<Button
size="small"
variant="outlined"
onClick={handleCancel}
startIcon={<CancelIcon />}
>
2023-08-17 15:55:44 +08:00
{i18n("cancel")}
</Button>
2024-03-14 18:06:28 +08:00
{!showMore && (
<Button
size="small"
variant="text"
onClick={() => {
setShowMore(true);
}}
>
{i18n("more")}
</Button>
)}
2023-08-17 15:55:44 +08:00
</Stack>
))}
2023-07-20 13:45:41 +08:00
</Stack>
</form>
);
}
2023-08-17 13:27:22 +08:00
function RuleAccordion({ rule, rules }) {
2023-09-10 14:51:16 +08:00
const i18n = useI18n();
2023-08-17 13:27:22 +08:00
const [expanded, setExpanded] = useState(false);
const handleChange = (e) => {
setExpanded((pre) => !pre);
};
return (
<Accordion expanded={expanded} onChange={handleChange}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
2023-08-17 15:55:44 +08:00
<Typography
2023-10-26 10:41:04 +08:00
sx={{
2023-08-17 15:55:44 +08:00
opacity: rules ? 1 : 0.5,
2023-10-26 10:41:04 +08:00
overflowWrap: "anywhere",
2023-08-17 15:55:44 +08:00
}}
>
2023-09-10 14:51:16 +08:00
{rule.pattern === GLOBAL_KEY
? `[${i18n("global_rule")}] ${rule.pattern}`
: rule.pattern}
2023-08-17 15:55:44 +08:00
</Typography>
2023-08-17 13:27:22 +08:00
</AccordionSummary>
<AccordionDetails>
{expanded && <RuleFields rule={rule} rules={rules} />}
</AccordionDetails>
</Accordion>
);
}
2023-08-31 00:18:57 +08:00
function ShareButton({ rules, injectRules, selectedUrl }) {
2023-08-20 23:30:08 +08:00
const alert = useAlert();
const i18n = useI18n();
const handleClick = async () => {
try {
2023-09-18 17:36:10 +08:00
const { syncType, syncUrl, syncKey } = await getSyncWithDefault();
if (syncType !== OPT_SYNCTYPE_WORKER || !syncUrl || !syncKey) {
2023-08-20 23:30:08 +08:00
alert.warning(i18n("error_sync_setting"));
return;
}
const shareRules = [...rules.list];
if (injectRules) {
2023-08-31 00:18:57 +08:00
const subRules = await loadOrFetchSubRules(selectedUrl);
2023-08-20 23:30:08 +08:00
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"));
2024-03-19 18:07:18 +08:00
kissLog(err, "share rules");
2023-08-20 23:30:08 +08:00
}
};
return (
<Button
size="small"
variant="outlined"
onClick={handleClick}
startIcon={<ShareIcon />}
>
2023-09-04 22:29:39 +08:00
{i18n("share")}
</Button>
);
}
2023-08-31 00:18:57 +08:00
function UserRules({ subRules }) {
2023-07-20 13:45:41 +08:00
const i18n = useI18n();
2023-07-22 09:00:33 +08:00
const rules = useRules();
2023-07-20 13:45:41 +08:00
const [showAdd, setShowAdd] = useState(false);
2023-08-31 00:18:57 +08:00
const { setting, updateSetting } = useSetting();
2023-08-22 17:37:42 +08:00
const [keyword, setKeyword] = useState("");
2023-08-17 15:55:44 +08:00
const injectRules = !!setting?.injectRules;
2023-08-31 00:18:57 +08:00
const { selectedUrl, selectedRules } = subRules;
2023-07-20 13:45:41 +08:00
2023-10-26 17:59:49 +08:00
const handleImport = async (data) => {
try {
await rules.merge(JSON.parse(data));
} catch (err) {
2024-03-19 18:07:18 +08:00
kissLog(err, "import rules");
2023-07-20 13:45:41 +08:00
}
};
2023-08-17 15:55:44 +08:00
const handleInject = () => {
updateSetting({
injectRules: !injectRules,
});
};
2023-08-22 17:37:42 +08:00
useEffect(() => {
if (!showAdd) {
setKeyword("");
}
}, [showAdd]);
2023-09-17 21:50:17 +08:00
if (!rules.list) {
return;
}
2023-07-20 13:45:41 +08:00
return (
2023-08-20 19:27:29 +08:00
<Stack spacing={3}>
2023-08-31 00:18:57 +08:00
<Stack
direction="row"
alignItems="center"
spacing={2}
useFlexGap
flexWrap="wrap"
>
2023-08-20 19:27:29 +08:00
<Button
size="small"
variant="contained"
disabled={showAdd}
onClick={(e) => {
e.preventDefault();
setShowAdd(true);
}}
2024-03-18 10:48:38 +08:00
startIcon={<AddIcon />}
2023-08-20 19:27:29 +08:00
>
{i18n("add")}
</Button>
2023-10-26 17:59:49 +08:00
<UploadButton text={i18n("import")} handleImport={handleImport} />
2023-08-20 19:27:29 +08:00
<DownloadButton
2024-04-16 16:29:59 +08:00
handleData={() => JSON.stringify([...rules.list].reverse(), null, 2)}
2023-08-20 19:27:29 +08:00
text={i18n("export")}
2023-10-26 23:55:05 +08:00
fileName={`kiss-rules_${Date.now()}.json`}
2023-08-20 19:27:29 +08:00
/>
2023-08-20 23:30:08 +08:00
<ShareButton
rules={rules}
injectRules={injectRules}
2023-08-31 00:18:57 +08:00
selectedUrl={selectedUrl}
2023-08-20 23:30:08 +08:00
/>
2023-09-08 15:32:42 +08:00
<Button
size="small"
variant="outlined"
onClick={() => {
rules.clear();
}}
startIcon={<ClearAllIcon />}
>
{i18n("clear_all")}
</Button>
2023-09-08 21:57:42 +08:00
<HelpButton url={URL_KISS_RULES_NEW_ISSUE} />
2023-09-04 22:29:39 +08:00
2023-08-20 19:27:29 +08:00
<FormControlLabel
control={
<Switch
size="small"
checked={injectRules}
onChange={handleInject}
/>
}
label={i18n("inject_rules")}
/>
</Stack>
2023-08-22 17:37:42 +08:00
{showAdd && (
<RuleFields
rules={rules}
setShow={setShowAdd}
setKeyword={setKeyword}
/>
)}
2023-08-20 19:27:29 +08:00
<Box>
2023-08-22 17:37:42 +08:00
{rules.list
.filter(
(rule) =>
rule.pattern.includes(keyword) || keyword.includes(rule.pattern)
)
.map((rule) => (
<RuleAccordion key={rule.pattern} rule={rule} rules={rules} />
))}
</Box>
2023-08-22 17:46:57 +08:00
{injectRules && (
<Box>
2023-08-31 00:18:57 +08:00
{selectedRules
2023-08-22 17:46:57 +08:00
.filter(
(rule) =>
rule.pattern.includes(keyword) || keyword.includes(rule.pattern)
)
.map((rule) => (
<RuleAccordion key={rule.pattern} rule={rule} />
))}
</Box>
)}
2023-08-20 19:27:29 +08:00
</Stack>
);
}
2023-09-08 15:16:10 +08:00
function SubRulesItem({
index,
url,
syncAt,
selectedUrl,
delSub,
setSelectedRules,
2023-09-11 17:56:31 +08:00
updateDataCache,
deleteDataCache,
2023-09-08 15:16:10 +08:00
}) {
2023-08-20 19:27:29 +08:00
const [loading, setLoading] = useState(false);
const handleDel = async () => {
try {
2023-08-31 00:18:57 +08:00
await delSub(url);
await delSubRules(url);
2023-09-11 17:56:31 +08:00
await deleteDataCache(url);
2023-08-20 19:27:29 +08:00
} catch (err) {
2024-03-19 18:07:18 +08:00
kissLog(err, "del subrules");
2023-08-20 19:27:29 +08:00
}
};
const handleSync = async () => {
try {
setLoading(true);
const rules = await syncSubRules(url);
if (rules.length > 0 && url === selectedUrl) {
2023-08-31 00:18:57 +08:00
setSelectedRules(rules);
2023-08-20 19:27:29 +08:00
}
2023-09-11 17:56:31 +08:00
await updateDataCache(url);
2023-08-20 19:27:29 +08:00
} catch (err) {
2024-03-19 18:07:18 +08:00
kissLog(err, "sync sub rules");
2023-08-20 19:27:29 +08:00
} finally {
setLoading(false);
}
};
return (
<Stack direction="row" alignItems="center" spacing={2}>
2023-10-26 10:41:04 +08:00
<FormControlLabel
value={url}
control={<Radio />}
sx={{
overflowWrap: "anywhere",
}}
label={url}
/>
2023-08-20 19:27:29 +08:00
2023-09-08 15:16:10 +08:00
{syncAt && (
2023-09-09 19:43:12 +08:00
<span style={{ marginLeft: "0.5em", opacity: 0.5 }}>
2023-09-08 15:16:10 +08:00
[{new Date(syncAt).toLocaleString()}]
</span>
)}
2023-08-20 19:27:29 +08:00
{loading ? (
<CircularProgress size={16} />
) : (
<IconButton size="small" onClick={handleSync}>
<SyncIcon fontSize="small" />
</IconButton>
)}
{index !== 0 && selectedUrl !== url && (
<IconButton size="small" onClick={handleDel}>
<DeleteIcon fontSize="small" />
</IconButton>
)}
</Stack>
);
}
2023-09-11 17:56:31 +08:00
function SubRulesEdit({ subList, addSub, updateDataCache }) {
2023-08-20 19:27:29 +08:00
const i18n = useI18n();
const [inputText, setInputText] = useState("");
const [inputError, setInputError] = useState("");
const [showInput, setShowInput] = useState(false);
2023-08-23 10:39:01 +08:00
const [loading, setLoading] = useState(false);
2023-08-20 19:27:29 +08:00
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;
}
2023-08-31 00:18:57 +08:00
if (subList.find((item) => item.url === url)) {
2023-08-20 19:27:29 +08:00
setInputError(i18n("error_duplicate_values"));
return;
}
try {
2023-08-23 10:39:01 +08:00
setLoading(true);
const rules = await syncSubRules(url);
2023-08-20 19:27:29 +08:00
if (rules.length === 0) {
throw new Error("empty rules");
}
2023-08-31 00:18:57 +08:00
await addSub(url);
2023-09-11 17:56:31 +08:00
await updateDataCache(url);
2023-08-20 19:27:29 +08:00
setShowInput(false);
setInputText("");
} catch (err) {
2024-03-19 18:07:18 +08:00
kissLog(err, "fetch rules");
2023-08-20 19:27:29 +08:00
setInputError(i18n("error_fetch_url"));
2023-08-23 10:39:01 +08:00
} finally {
setLoading(false);
2023-08-20 19:27:29 +08:00
}
};
const handleInput = (e) => {
e.preventDefault();
setInputText(e.target.value);
};
const handleFocus = (e) => {
e.preventDefault();
setInputError("");
};
return (
<>
<Stack direction="row" alignItems="center" spacing={2}>
<Button
size="small"
variant="contained"
disabled={showInput}
onClick={(e) => {
e.preventDefault();
setShowInput(true);
}}
2024-03-18 10:48:38 +08:00
startIcon={<AddIcon />}
2023-08-20 19:27:29 +08:00
>
{i18n("add")}
</Button>
2023-09-08 21:57:42 +08:00
<HelpButton url={URL_KISS_RULES_NEW_ISSUE} />
2023-08-20 19:27:29 +08:00
</Stack>
{showInput && (
<>
<TextField
2023-07-20 13:45:41 +08:00
size="small"
2023-08-20 19:27:29 +08:00
value={inputText}
error={!!inputError}
helperText={inputError}
onChange={handleInput}
onFocus={handleFocus}
label={i18n("subscribe_url")}
2023-07-20 13:45:41 +08:00
/>
2023-08-17 15:55:44 +08:00
2023-08-20 19:27:29 +08:00
<Stack direction="row" alignItems="center" spacing={2}>
2023-08-23 10:39:01 +08:00
<Button
size="small"
variant="contained"
onClick={handleSave}
disabled={loading}
2024-03-18 10:48:38 +08:00
startIcon={<SaveIcon />}
2023-08-23 10:39:01 +08:00
>
2023-08-20 19:27:29 +08:00
{i18n("save")}
</Button>
2024-03-18 10:48:38 +08:00
<Button
size="small"
variant="outlined"
onClick={handleCancel}
startIcon={<CancelIcon />}
>
2023-08-20 19:27:29 +08:00
{i18n("cancel")}
</Button>
</Stack>
</>
)}
</>
);
}
2023-07-20 13:45:41 +08:00
2023-08-31 00:18:57 +08:00
function SubRules({ subRules }) {
const {
subList,
selectSub,
addSub,
delSub,
selectedUrl,
selectedRules,
setSelectedRules,
loading,
} = subRules;
2023-09-11 22:53:04 +08:00
const { dataCaches, updateDataCache, deleteDataCache, reloadSync } =
2023-09-11 17:56:31 +08:00
useSyncCaches();
2023-07-20 13:45:41 +08:00
2023-08-20 19:27:29 +08:00
const handleSelect = (e) => {
const url = e.target.value;
2023-08-31 00:18:57 +08:00
selectSub(url);
2023-08-20 19:27:29 +08:00
};
2023-09-11 22:53:04 +08:00
useEffect(() => {
reloadSync();
}, [selectedRules, reloadSync]);
2023-08-20 19:27:29 +08:00
return (
<Stack spacing={3}>
2023-09-11 17:56:31 +08:00
<SubRulesEdit
subList={subList}
addSub={addSub}
updateDataCache={updateDataCache}
/>
2023-08-20 19:27:29 +08:00
2023-08-31 00:18:57 +08:00
<RadioGroup value={selectedUrl} onChange={handleSelect}>
{subList.map((item, index) => (
2023-08-20 19:27:29 +08:00
<SubRulesItem
key={item.url}
url={item.url}
2023-09-11 17:56:31 +08:00
syncAt={dataCaches[item.url]}
2023-08-20 19:27:29 +08:00
index={index}
2023-08-31 00:18:57 +08:00
selectedUrl={selectedUrl}
delSub={delSub}
setSelectedRules={setSelectedRules}
2023-09-11 17:56:31 +08:00
updateDataCache={updateDataCache}
deleteDataCache={deleteDataCache}
2023-08-20 19:27:29 +08:00
/>
))}
</RadioGroup>
<Box>
{loading ? (
<center>
<CircularProgress />
</center>
) : (
2023-08-31 00:18:57 +08:00
selectedRules.map((rule) => (
<RuleAccordion key={rule.pattern} rule={rule} />
))
2023-08-17 15:55:44 +08:00
)}
2023-08-20 19:27:29 +08:00
</Box>
</Stack>
);
}
export default function Rules() {
const i18n = useI18n();
const [activeTab, setActiveTab] = useState(0);
2023-08-31 00:18:57 +08:00
const subRules = useSubRules();
2023-08-20 19:27:29 +08:00
const handleTabChange = (e, newValue) => {
setActiveTab(newValue);
};
return (
<Box>
<Stack spacing={3}>
2023-08-22 10:35:57 +08:00
<Alert severity="info">
{i18n("rules_warn_1")}
<br />
{i18n("rules_warn_2")}
2024-03-18 10:48:38 +08:00
<br />
{i18n("rules_warn_3")}
2023-08-22 10:35:57 +08:00
</Alert>
2023-08-20 19:27:29 +08:00
<Box sx={{ borderBottom: 1, borderColor: "divider" }}>
<Tabs value={activeTab} onChange={handleTabChange}>
2023-08-22 10:35:57 +08:00
<Tab label={i18n("personal_rules")} />
2023-08-20 19:27:29 +08:00
<Tab label={i18n("subscribe_rules")} />
2023-09-01 22:27:25 +08:00
<Tab label={i18n("overwrite_subscribe_rules")} />
2023-08-20 19:27:29 +08:00
</Tabs>
</Box>
2023-08-31 00:18:57 +08:00
<div hidden={activeTab !== 0}>
{activeTab === 0 && <UserRules subRules={subRules} />}
</div>
<div hidden={activeTab !== 1}>
{activeTab === 1 && <SubRules subRules={subRules} />}
</div>
2023-09-01 22:27:25 +08:00
<div hidden={activeTab !== 2}>{activeTab === 2 && <OwSubRule />}</div>
2023-07-20 13:45:41 +08:00
</Stack>
</Box>
);
}