support diy text styles
This commit is contained in:
@@ -15,6 +15,7 @@
|
|||||||
"react-markdown": "^8.0.7",
|
"react-markdown": "^8.0.7",
|
||||||
"react-router-dom": "^6.10.0",
|
"react-router-dom": "^6.10.0",
|
||||||
"react-scripts": "5.0.1",
|
"react-scripts": "5.0.1",
|
||||||
|
"styled-components": "^6.0.7",
|
||||||
"webextension-polyfill": "^0.10.0"
|
"webextension-polyfill": "^0.10.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -188,6 +188,14 @@ export const I18N = {
|
|||||||
zh: `高亮`,
|
zh: `高亮`,
|
||||||
en: `Highlight`,
|
en: `Highlight`,
|
||||||
},
|
},
|
||||||
|
diy_style: {
|
||||||
|
zh: `自定义样式`,
|
||||||
|
en: `Custom Style`,
|
||||||
|
},
|
||||||
|
diy_style_helper: {
|
||||||
|
zh: `遵循“styled-components”的语法`,
|
||||||
|
en: `Follow the syntax of "styled-components"`,
|
||||||
|
},
|
||||||
setting: {
|
setting: {
|
||||||
zh: `设置`,
|
zh: `设置`,
|
||||||
en: `Setting`,
|
en: `Setting`,
|
||||||
@@ -201,8 +209,8 @@ export const I18N = {
|
|||||||
en: `1. The asterisk (*) wildcard is supported. 2. Multiple URLs can be separated by English commas ",".`,
|
en: `1. The asterisk (*) wildcard is supported. 2. Multiple URLs can be separated by English commas ",".`,
|
||||||
},
|
},
|
||||||
selector_helper: {
|
selector_helper: {
|
||||||
zh: `1、遵循CSS选择器规则。2、留空表示采用全局设置。3、多个CSS选择器之间用“;”隔开。4、“shadow root”选择器和内部选择器用“>>>”隔开。`,
|
zh: `1、遵循CSS选择器语法。2、留空表示采用全局设置。3、多个CSS选择器之间用“;”隔开。4、“shadow root”选择器和内部选择器用“>>>”隔开。`,
|
||||||
en: `1. Follow the CSS selector rules. 2. Leave blank to adopt the global setting. 3. Separate multiple CSS selectors with ";". 4. The "shadow root" selector and the internal selector are separated by ">>>".`,
|
en: `1. Follow CSS selector syntax. 2. Leave blank to adopt the global setting. 3. Separate multiple CSS selectors with ";". 4. The "shadow root" selector and the internal selector are separated by ">>>".`,
|
||||||
},
|
},
|
||||||
translate_switch: {
|
translate_switch: {
|
||||||
zh: `开启翻译`,
|
zh: `开启翻译`,
|
||||||
|
|||||||
@@ -122,7 +122,8 @@ export const OPT_STYLE_DOTLINE = "dot_line"; // 点状线
|
|||||||
export const OPT_STYLE_DASHLINE = "dash_line"; // 虚线
|
export const OPT_STYLE_DASHLINE = "dash_line"; // 虚线
|
||||||
export const OPT_STYLE_WAVYLINE = "wavy_line"; // 波浪线
|
export const OPT_STYLE_WAVYLINE = "wavy_line"; // 波浪线
|
||||||
export const OPT_STYLE_FUZZY = "fuzzy"; // 模糊
|
export const OPT_STYLE_FUZZY = "fuzzy"; // 模糊
|
||||||
export const OPT_STYLE_HIGHTLIGHT = "highlight"; // 高亮
|
export const OPT_STYLE_HIGHLIGHT = "highlight"; // 高亮
|
||||||
|
export const OPT_STYLE_DIY = "diy_style"; // 自定义样式
|
||||||
export const OPT_STYLE_ALL = [
|
export const OPT_STYLE_ALL = [
|
||||||
OPT_STYLE_NONE,
|
OPT_STYLE_NONE,
|
||||||
OPT_STYLE_LINE,
|
OPT_STYLE_LINE,
|
||||||
@@ -130,7 +131,15 @@ export const OPT_STYLE_ALL = [
|
|||||||
OPT_STYLE_DASHLINE,
|
OPT_STYLE_DASHLINE,
|
||||||
OPT_STYLE_WAVYLINE,
|
OPT_STYLE_WAVYLINE,
|
||||||
OPT_STYLE_FUZZY,
|
OPT_STYLE_FUZZY,
|
||||||
OPT_STYLE_HIGHTLIGHT,
|
OPT_STYLE_HIGHLIGHT,
|
||||||
|
OPT_STYLE_DIY,
|
||||||
|
];
|
||||||
|
export const OPT_STYLE_USE_COLOR = [
|
||||||
|
OPT_STYLE_LINE,
|
||||||
|
OPT_STYLE_DOTLINE,
|
||||||
|
OPT_STYLE_DASHLINE,
|
||||||
|
OPT_STYLE_WAVYLINE,
|
||||||
|
OPT_STYLE_HIGHLIGHT,
|
||||||
];
|
];
|
||||||
|
|
||||||
export const DEFAULT_FETCH_LIMIT = 10; // 默认最大任务数量
|
export const DEFAULT_FETCH_LIMIT = 10; // 默认最大任务数量
|
||||||
@@ -141,6 +150,19 @@ export const PROMPT_PLACE_TO = "{{to}}"; // 占位符
|
|||||||
|
|
||||||
export const DEFAULT_COLOR = "#209CEE"; // 默认高亮背景色/线条颜色
|
export const DEFAULT_COLOR = "#209CEE"; // 默认高亮背景色/线条颜色
|
||||||
|
|
||||||
|
export const DEFAULT_DIY_STYLE = `color: #666;
|
||||||
|
background: linear-gradient(
|
||||||
|
45deg,
|
||||||
|
LightGreen 20%,
|
||||||
|
LightPink 20% 40%,
|
||||||
|
LightSalmon 40% 60%,
|
||||||
|
LightSeaGreen 60% 80%,
|
||||||
|
LightSkyBlue 80%
|
||||||
|
);
|
||||||
|
&:hover {
|
||||||
|
color: #333;
|
||||||
|
};`;
|
||||||
|
|
||||||
// 全局规则
|
// 全局规则
|
||||||
export const GLOBLA_RULE = {
|
export const GLOBLA_RULE = {
|
||||||
pattern: "*",
|
pattern: "*",
|
||||||
@@ -151,6 +173,7 @@ export const GLOBLA_RULE = {
|
|||||||
textStyle: OPT_STYLE_DASHLINE,
|
textStyle: OPT_STYLE_DASHLINE,
|
||||||
transOpen: "false",
|
transOpen: "false",
|
||||||
bgColor: "",
|
bgColor: "",
|
||||||
|
textDiyStyle: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
// 订阅列表
|
// 订阅列表
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export const DEFAULT_RULE = {
|
|||||||
textStyle: GLOBAL_KEY,
|
textStyle: GLOBAL_KEY,
|
||||||
transOpen: GLOBAL_KEY,
|
transOpen: GLOBAL_KEY,
|
||||||
bgColor: "",
|
bgColor: "",
|
||||||
|
textDiyStyle: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
const RULES = [
|
const RULES = [
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ export const matchRule = async (
|
|||||||
GLOBLA_RULE.selector;
|
GLOBLA_RULE.selector;
|
||||||
|
|
||||||
rule.bgColor = rule?.bgColor?.trim() || globalRule?.bgColor?.trim();
|
rule.bgColor = rule?.bgColor?.trim() || globalRule?.bgColor?.trim();
|
||||||
|
rule.textDiyStyle = rule?.textDiyStyle?.trim() || globalRule?.textDiyStyle?.trim();
|
||||||
|
|
||||||
["translator", "fromLang", "toLang", "textStyle", "transOpen"].forEach(
|
["translator", "fromLang", "toLang", "textStyle", "transOpen"].forEach(
|
||||||
(key) => {
|
(key) => {
|
||||||
@@ -99,10 +100,12 @@ export const checkRules = (rules) => {
|
|||||||
textStyle,
|
textStyle,
|
||||||
transOpen,
|
transOpen,
|
||||||
bgColor,
|
bgColor,
|
||||||
|
textDiyStyle,
|
||||||
}) => ({
|
}) => ({
|
||||||
pattern: pattern.trim(),
|
pattern: pattern.trim(),
|
||||||
selector: type(selector) === "string" ? selector : "",
|
selector: type(selector) === "string" ? selector : "",
|
||||||
bgColor: type(bgColor) === "string" ? bgColor : "",
|
bgColor: type(bgColor) === "string" ? bgColor : "",
|
||||||
|
textDiyStyle: type(textDiyStyle) === "string" ? textDiyStyle : "",
|
||||||
translator: matchValue([GLOBAL_KEY, ...OPT_TRANS_ALL], translator),
|
translator: matchValue([GLOBAL_KEY, ...OPT_TRANS_ALL], translator),
|
||||||
fromLang: matchValue([GLOBAL_KEY, ...fromLangs], fromLang),
|
fromLang: matchValue([GLOBAL_KEY, ...fromLangs], fromLang),
|
||||||
toLang: matchValue([GLOBAL_KEY, ...toLangs], toLang),
|
toLang: matchValue([GLOBAL_KEY, ...toLangs], toLang),
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useMemo, useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import LoadingIcon from "./LoadingIcon";
|
import LoadingIcon from "./LoadingIcon";
|
||||||
import {
|
import {
|
||||||
OPT_STYLE_LINE,
|
OPT_STYLE_LINE,
|
||||||
@@ -6,30 +6,101 @@ import {
|
|||||||
OPT_STYLE_DASHLINE,
|
OPT_STYLE_DASHLINE,
|
||||||
OPT_STYLE_WAVYLINE,
|
OPT_STYLE_WAVYLINE,
|
||||||
OPT_STYLE_FUZZY,
|
OPT_STYLE_FUZZY,
|
||||||
OPT_STYLE_HIGHTLIGHT,
|
OPT_STYLE_HIGHLIGHT,
|
||||||
|
OPT_STYLE_DIY,
|
||||||
DEFAULT_COLOR,
|
DEFAULT_COLOR,
|
||||||
EVENT_KISS,
|
EVENT_KISS,
|
||||||
MSG_TRANS_CURRULE,
|
MSG_TRANS_CURRULE,
|
||||||
TRANS_NEWLINE_LENGTH,
|
TRANS_NEWLINE_LENGTH,
|
||||||
} from "../../config";
|
} from "../../config";
|
||||||
import { useTranslate } from "../../hooks/Translate";
|
import { useTranslate } from "../../hooks/Translate";
|
||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
const LineSpan = styled.span`
|
||||||
|
opacity: 0.6;
|
||||||
|
text-decoration-line: underline;
|
||||||
|
text-decoration-style: ${(props) => props.$lineStyle};
|
||||||
|
text-decoration-color: ${(props) => props.$lineColor};
|
||||||
|
text-decoration-thickness: 2px;
|
||||||
|
text-underline-offset: 0.3em;
|
||||||
|
-webkit-text-decoration-line: underline;
|
||||||
|
-webkit-text-decoration-style: ${(props) => props.$lineStyle};
|
||||||
|
-webkit-text-decoration-color: ${(props) => props.$lineColor};
|
||||||
|
-webkit-text-decoration-thickness: 2px;
|
||||||
|
-webkit-text-underline-offset: 0.3em;
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const FuzzySpan = styled.span`
|
||||||
|
filter: blur(5px);
|
||||||
|
transition: filter 0.2s ease-in-out;
|
||||||
|
&hover: {
|
||||||
|
filter: none;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const HighlightSpan = styled.span`
|
||||||
|
coler: #fff;
|
||||||
|
background-color: ${(props) => props.$bgColor};
|
||||||
|
&hover: {
|
||||||
|
filter: none;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const DiySpan = styled.span`
|
||||||
|
${(props) => props.$diyStyle}
|
||||||
|
`;
|
||||||
|
|
||||||
|
function StyledSpan({ textStyle, textDiyStyle, bgColor, children }) {
|
||||||
|
switch (textStyle) {
|
||||||
|
case OPT_STYLE_LINE: // 下划线
|
||||||
|
return (
|
||||||
|
<LineSpan $lineStyle="solid" $lineColor={bgColor}>
|
||||||
|
{children}
|
||||||
|
</LineSpan>
|
||||||
|
);
|
||||||
|
case OPT_STYLE_DOTLINE: // 点状线
|
||||||
|
return (
|
||||||
|
<LineSpan $lineStyle="dotted" $lineColor={bgColor}>
|
||||||
|
{children}
|
||||||
|
</LineSpan>
|
||||||
|
);
|
||||||
|
case OPT_STYLE_DASHLINE: // 虚线
|
||||||
|
return (
|
||||||
|
<LineSpan $lineStyle="dashed" $lineColor={bgColor}>
|
||||||
|
{children}
|
||||||
|
</LineSpan>
|
||||||
|
);
|
||||||
|
case OPT_STYLE_WAVYLINE: // 波浪线
|
||||||
|
return (
|
||||||
|
<LineSpan $lineStyle="wavy" $lineColor={bgColor}>
|
||||||
|
{children}
|
||||||
|
</LineSpan>
|
||||||
|
);
|
||||||
|
case OPT_STYLE_FUZZY: // 模糊
|
||||||
|
return <FuzzySpan>{children}</FuzzySpan>;
|
||||||
|
case OPT_STYLE_HIGHLIGHT: // 高亮
|
||||||
|
return (
|
||||||
|
<HighlightSpan $bgColor={bgColor || DEFAULT_COLOR}>
|
||||||
|
{children}
|
||||||
|
</HighlightSpan>
|
||||||
|
);
|
||||||
|
case OPT_STYLE_DIY: // 自定义
|
||||||
|
return <DiySpan $diyStyle={textDiyStyle}>{children}</DiySpan>;
|
||||||
|
default:
|
||||||
|
return <span>{children}</span>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default function Content({ q, translator }) {
|
export default function Content({ q, translator }) {
|
||||||
const [rule, setRule] = useState(translator.rule);
|
const [rule, setRule] = useState(translator.rule);
|
||||||
const [hover, setHover] = useState(false);
|
|
||||||
const { text, sameLang, loading } = useTranslate(q, rule, translator.setting);
|
const { text, sameLang, loading } = useTranslate(q, rule, translator.setting);
|
||||||
const { textStyle, bgColor } = rule;
|
const { textStyle, bgColor = "", textDiyStyle = "" } = rule;
|
||||||
|
|
||||||
const { newlineLength = TRANS_NEWLINE_LENGTH } = translator.setting;
|
const { newlineLength = TRANS_NEWLINE_LENGTH } = translator.setting;
|
||||||
|
|
||||||
const handleMouseEnter = () => {
|
|
||||||
setHover(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMouseLeave = () => {
|
|
||||||
setHover(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleKissEvent = (e) => {
|
const handleKissEvent = (e) => {
|
||||||
const { action, args } = e.detail;
|
const { action, args } = e.detail;
|
||||||
switch (action) {
|
switch (action) {
|
||||||
@@ -37,7 +108,6 @@ export default function Content({ q, translator }) {
|
|||||||
setRule(args);
|
setRule(args);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// console.log(`[popup] kissEvent action skip: ${action}`);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -48,45 +118,6 @@ export default function Content({ q, translator }) {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const style = useMemo(() => {
|
|
||||||
const lineColor = bgColor || "";
|
|
||||||
const underlineStyle = (st) => ({
|
|
||||||
opacity: hover ? 1 : 0.6,
|
|
||||||
textDecorationLine: "underline",
|
|
||||||
textDecorationColor: lineColor,
|
|
||||||
textDecorationStyle: st,
|
|
||||||
textDecorationThickness: "2px",
|
|
||||||
textUnderlineOffset: "0.3em",
|
|
||||||
WebkittextDecorationLine: "underline",
|
|
||||||
WebkittextDecorationColor: lineColor,
|
|
||||||
WebkittextDecorationStyle: st,
|
|
||||||
WebkittextDecorationThickness: "2px",
|
|
||||||
WebkittextTextUnderlineOffset: "0.3em",
|
|
||||||
});
|
|
||||||
switch (textStyle) {
|
|
||||||
case OPT_STYLE_LINE: // 下划线
|
|
||||||
return underlineStyle("solid");
|
|
||||||
case OPT_STYLE_DOTLINE: // 点状线
|
|
||||||
return underlineStyle("dotted");
|
|
||||||
case OPT_STYLE_DASHLINE: // 虚线
|
|
||||||
return underlineStyle("dashed");
|
|
||||||
case OPT_STYLE_WAVYLINE: // 波浪线
|
|
||||||
return underlineStyle("wavy");
|
|
||||||
case OPT_STYLE_FUZZY: // 模糊
|
|
||||||
return {
|
|
||||||
filter: hover ? "none" : "blur(5px)",
|
|
||||||
transition: "filter 0.2s ease-in-out",
|
|
||||||
};
|
|
||||||
case OPT_STYLE_HIGHTLIGHT: // 高亮
|
|
||||||
return {
|
|
||||||
color: "#FFF",
|
|
||||||
backgroundColor: bgColor || DEFAULT_COLOR,
|
|
||||||
};
|
|
||||||
default:
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}, [textStyle, hover, bgColor]);
|
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -100,13 +131,13 @@ export default function Content({ q, translator }) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{q.length > newlineLength ? <br /> : " "}
|
{q.length > newlineLength ? <br /> : " "}
|
||||||
<span
|
<StyledSpan
|
||||||
style={style}
|
textStyle={textStyle}
|
||||||
onMouseEnter={handleMouseEnter}
|
textDiyStyle={textDiyStyle}
|
||||||
onMouseLeave={handleMouseLeave}
|
bgColor={bgColor}
|
||||||
>
|
>
|
||||||
{text}
|
{text}
|
||||||
</span>
|
</StyledSpan>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import {
|
|||||||
OPT_LANGS_TO,
|
OPT_LANGS_TO,
|
||||||
OPT_TRANS_ALL,
|
OPT_TRANS_ALL,
|
||||||
OPT_STYLE_ALL,
|
OPT_STYLE_ALL,
|
||||||
|
OPT_STYLE_DIY,
|
||||||
|
OPT_STYLE_USE_COLOR,
|
||||||
} from "../../config";
|
} from "../../config";
|
||||||
import { useState, useRef, useEffect, useMemo } from "react";
|
import { useState, useRef, useEffect, useMemo } from "react";
|
||||||
import { useI18n } from "../../hooks/I18n";
|
import { useI18n } from "../../hooks/I18n";
|
||||||
@@ -44,7 +46,10 @@ import { debounce } from "../../libs/utils";
|
|||||||
import { delSubRules, getSyncWithDefault } from "../../libs/storage";
|
import { delSubRules, getSyncWithDefault } from "../../libs/storage";
|
||||||
|
|
||||||
function RuleFields({ rule, rules, setShow, setKeyword }) {
|
function RuleFields({ rule, rules, setShow, setKeyword }) {
|
||||||
const initFormValues = rule || { ...DEFAULT_RULE, transOpen: "true" };
|
const initFormValues = rule || {
|
||||||
|
...DEFAULT_RULE,
|
||||||
|
transOpen: "true",
|
||||||
|
};
|
||||||
const editMode = !!rule;
|
const editMode = !!rule;
|
||||||
|
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
@@ -60,6 +65,7 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
|
|||||||
textStyle,
|
textStyle,
|
||||||
transOpen,
|
transOpen,
|
||||||
bgColor,
|
bgColor,
|
||||||
|
textDiyStyle,
|
||||||
} = formValues;
|
} = formValues;
|
||||||
|
|
||||||
const hasSamePattern = (str) => {
|
const hasSamePattern = (str) => {
|
||||||
@@ -262,6 +268,7 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
|
|||||||
))}
|
))}
|
||||||
</TextField>
|
</TextField>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
{OPT_STYLE_USE_COLOR.includes(textStyle) && (
|
||||||
<Grid item xs={12} sm={6} md={3} lg={2}>
|
<Grid item xs={12} sm={6} md={3} lg={2}>
|
||||||
<TextField
|
<TextField
|
||||||
size="small"
|
size="small"
|
||||||
@@ -273,9 +280,23 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
|
|||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
)}
|
||||||
</Grid>
|
</Grid>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{textStyle === OPT_STYLE_DIY && (
|
||||||
|
<TextField
|
||||||
|
size="small"
|
||||||
|
label={i18n("diy_style")}
|
||||||
|
helperText={i18n("diy_style_helper")}
|
||||||
|
name="textDiyStyle"
|
||||||
|
value={textDiyStyle}
|
||||||
|
disabled={disabled}
|
||||||
|
onChange={handleChange}
|
||||||
|
multiline
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{rules &&
|
{rules &&
|
||||||
(editMode ? (
|
(editMode ? (
|
||||||
// 编辑
|
// 编辑
|
||||||
|
|||||||
Reference in New Issue
Block a user