feat: Supports setting multiple custom styles
This commit is contained in:
@@ -614,15 +614,10 @@ export const I18N = {
|
||||
en: `Glow`,
|
||||
zh_TW: `發光`,
|
||||
},
|
||||
diy_style: {
|
||||
zh: `自定义样式`,
|
||||
en: `Custom Style`,
|
||||
zh_TW: `自訂樣式`,
|
||||
},
|
||||
diy_style_helper: {
|
||||
zh: `遵循“CSS”的语法`,
|
||||
en: `Follow the syntax of "CSS"`,
|
||||
zh_TW: `遵循 CSS 語法`,
|
||||
colorful: {
|
||||
zh: `多彩`,
|
||||
en: `Colorful`,
|
||||
zh_TW: `多彩`,
|
||||
},
|
||||
setting: {
|
||||
zh: `设置`,
|
||||
@@ -1804,6 +1799,21 @@ export const I18N = {
|
||||
en: `Early triggering of scroll loading (0-10000px)`,
|
||||
zh_TW: `滾動載入提前觸發 (0-10000px)`,
|
||||
},
|
||||
styles_setting: {
|
||||
zh: `样式设置`,
|
||||
en: `Style Setting`,
|
||||
zh_TW: `樣式設定`,
|
||||
},
|
||||
style_name: {
|
||||
zh: `样式名称`,
|
||||
en: `Style Name`,
|
||||
zh_TW: `樣式名稱`,
|
||||
},
|
||||
style_code: {
|
||||
zh: `样式代码`,
|
||||
en: `Style Code`,
|
||||
zh_TW: `樣式程式碼`,
|
||||
},
|
||||
};
|
||||
|
||||
export const newI18n = (lang) => (key) => I18N[key]?.[lang] || "";
|
||||
|
||||
@@ -7,3 +7,4 @@ export * from "./storage";
|
||||
export * from "./url";
|
||||
export * from "./msg";
|
||||
export * from "./client";
|
||||
export * from "./styles";
|
||||
|
||||
407
src/config/quotes.js
Normal file
407
src/config/quotes.js
Normal file
@@ -0,0 +1,407 @@
|
||||
const quotes = [
|
||||
{
|
||||
en: "The unexamined life is not worth living.",
|
||||
zh: "未经审视的人生不值得过。",
|
||||
},
|
||||
{
|
||||
en: "I think, therefore I am.",
|
||||
zh: "我思故我在。",
|
||||
},
|
||||
{
|
||||
en: "He who has a why to live for can bear almost any how.",
|
||||
zh: "知道为何而活的人,几乎能忍受任何一种生活。",
|
||||
},
|
||||
{
|
||||
en: "Life is what happens when you're busy making other plans.",
|
||||
zh: "生活就是当你忙着制定其他计划时所发生的事情。",
|
||||
},
|
||||
{
|
||||
en: "Get busy living or get busy dying.",
|
||||
zh: "要么忙着活,要么忙着死。",
|
||||
},
|
||||
{
|
||||
en: "We are what we repeatedly do. Excellence, then, is not an act, but a habit.",
|
||||
zh: "我们由我们反复做的事情构成的。因此,卓越不是一种行为,而是一种习惯。",
|
||||
},
|
||||
{
|
||||
en: "Man is condemned to be free.",
|
||||
zh: "人注定是自由的。",
|
||||
},
|
||||
{
|
||||
en: "To be, or not to be: that is the question.",
|
||||
zh: "生存还是毁灭,这是一个问题。",
|
||||
},
|
||||
{
|
||||
en: "The purpose of life is not to be happy. It is to be useful, to be honorable, to be compassionate, to have it make some difference that you have lived and lived well.",
|
||||
zh: "人生的目的不是快乐,而是有用、高尚、富有同情心,让你活过并且活得好,从而使世界有所不同。",
|
||||
},
|
||||
{
|
||||
en: "Life is 10% what happens to us and 90% how we react to it.",
|
||||
zh: "生活 10% 取决于发生在我们身上的事,90% 取决于我们如何反应。",
|
||||
},
|
||||
{
|
||||
en: "The two most important days in your life are the day you are born and the day you find out why.",
|
||||
zh: "你一生中最重要的两天是:你出生的那天和你明白你为何出生的那天。",
|
||||
},
|
||||
{
|
||||
en: "In three words I can sum up everything I've learned about life: it goes on.",
|
||||
zh: "关于人生,我所学到的一切可以总结为三个词:它在继续。",
|
||||
},
|
||||
{
|
||||
en: "Not all those who wander are lost.",
|
||||
zh: "并非所有流浪者都迷失了方向。",
|
||||
},
|
||||
{
|
||||
en: "Life is simple, but we insist on making it complicated.",
|
||||
zh: "生活本简单,但我们坚持要把它弄复杂。",
|
||||
},
|
||||
{
|
||||
en: "Our life is what our thoughts make it.",
|
||||
zh: "我们的生活是由我们的思想造成的。",
|
||||
},
|
||||
{
|
||||
en: "Find purpose, the means will follow.",
|
||||
zh: "找到目标,方法自会随之而来。",
|
||||
},
|
||||
{
|
||||
en: "The goal of life is living in agreement with nature.",
|
||||
zh: "生活的目标是与自然和谐相处。",
|
||||
},
|
||||
{
|
||||
en: "The only true wisdom is in knowing you know nothing.",
|
||||
zh: "唯一的真正智慧在于知道自己一无所有。",
|
||||
},
|
||||
{
|
||||
en: "Knowledge is power.",
|
||||
zh: "知识就是力量。",
|
||||
},
|
||||
{
|
||||
en: "Knowing yourself is the beginning of all wisdom.",
|
||||
zh: "了解自己是所有智慧的开端。",
|
||||
},
|
||||
{
|
||||
en: "The journey of a thousand miles begins with a single step.",
|
||||
zh: "千里之行,始于足下。",
|
||||
},
|
||||
{
|
||||
en: "The only source of knowledge is experience.",
|
||||
zh: "知识的唯一来源是经验。",
|
||||
},
|
||||
{
|
||||
en: "A fool thinks himself to be wise, but a wise man knows himself to be a fool.",
|
||||
zh: "愚者自以为聪明,智者自知愚蠢。",
|
||||
},
|
||||
{
|
||||
en: "We learn from failure, not from success!",
|
||||
zh: "我们从失败中学习,而不是从成功中!",
|
||||
},
|
||||
{
|
||||
en: "The wise man is one who knows what he does not know.",
|
||||
zh: "智者,知其所不知。",
|
||||
},
|
||||
{
|
||||
en: "To know that we know what we know, and that we do not know what we do not know, that is true knowledge.",
|
||||
zh: "知之为知之,不知为不知,是知也。",
|
||||
},
|
||||
{
|
||||
en: "Curiosity is the wick in the candle of learning.",
|
||||
zh: "好奇心是学习这支蜡烛的灯芯。",
|
||||
},
|
||||
{
|
||||
en: "It is the mark of an educated mind to be able to entertain a thought without accepting it.",
|
||||
zh: "能够容纳一种思想而不同意它,这是一个受过教育的头脑的标志。",
|
||||
},
|
||||
{
|
||||
en: "Never stop questioning.",
|
||||
zh: "永远不要停止提问。",
|
||||
},
|
||||
{
|
||||
en: "The man who asks a question is a fool for a minute, the man who does not ask is a fool for life.",
|
||||
zh: "问问题的人,只傻一分钟;不问的人,傻一生。",
|
||||
},
|
||||
{
|
||||
en: "Wisdom is not a product of schooling but of the lifelong attempt to acquire it.",
|
||||
zh: "智慧不是学校教育的产物,而是终生努力获得的产物。",
|
||||
},
|
||||
{
|
||||
en: "The greatest enemy of knowledge is not ignorance, it is the illusion of knowledge.",
|
||||
zh: "知识最大的敌人不是无知,而是自以为拥有知识的幻觉。",
|
||||
},
|
||||
{
|
||||
en: "True wisdom comes to each of us when we realize how little we understand about life, ourselves, and the world around us.",
|
||||
zh: "当我们认识到自己对生命、对自身、对周围世界了解得多么少时,真正的智慧才会降临到我们每个人身上。",
|
||||
},
|
||||
{
|
||||
en: "Beware of false knowledge; it is more dangerous than ignorance.",
|
||||
zh: "谨防虚假的知识;它比无知更危险。",
|
||||
},
|
||||
{
|
||||
en: "What does not kill me makes me stronger.",
|
||||
zh: "杀不死我的,使我更强大。",
|
||||
},
|
||||
{
|
||||
en: "The only constant in life is change.",
|
||||
zh: "生活中唯一不变的就是变化。",
|
||||
},
|
||||
{
|
||||
en: "If you are going through hell, keep going.",
|
||||
zh: "如果你正在经历地狱,那就继续走下去。",
|
||||
},
|
||||
{
|
||||
en: "In the middle of difficulty lies opportunity.",
|
||||
zh: "机会蕴藏在困难之中。",
|
||||
},
|
||||
{
|
||||
en: "It is not the strongest of the species that survive, nor the most intelligent, but the one most responsive to change.",
|
||||
zh: "存活下来的物种不是最强壮的,也不是最聪明的,而是最能适应变化的。",
|
||||
},
|
||||
{
|
||||
en: "We must become the change we wish to see in the world.",
|
||||
zh: "我们必须成为我们希望在世界上看到的改变。",
|
||||
},
|
||||
{
|
||||
en: "A smooth sea never made a skilled sailor.",
|
||||
zh: "平静的大海练不出熟练的水手。",
|
||||
},
|
||||
{
|
||||
en: "Obstacles don't block the path, they are the path.",
|
||||
zh: "障碍不是挡住了路,障碍本身就是路。",
|
||||
},
|
||||
{
|
||||
en: "Fall seven times, stand up eight.",
|
||||
zh: "七次跌倒,八次站起。",
|
||||
},
|
||||
{
|
||||
en: "The art of life lies in a constant readjustment to our surroundings.",
|
||||
zh: "生活的艺术在于不断地调整自己以适应环境。",
|
||||
},
|
||||
{
|
||||
en: "Adversity introduces a man to himself.",
|
||||
zh: "逆境使人认识自己。",
|
||||
},
|
||||
{
|
||||
en: "The wound is the place where the Light enters you.",
|
||||
zh: "伤口是光进入你内心的入口。",
|
||||
},
|
||||
{
|
||||
en: "When we are no longer able to change a situation, we are challenged to change ourselves.",
|
||||
zh: "当我们无法改变现状时,我们就需要改变自己。",
|
||||
},
|
||||
{
|
||||
en: "Be the change you wish to see in the world.",
|
||||
zh: "成为你希望在世界上看到的改变。",
|
||||
},
|
||||
{
|
||||
en: "Do not pray for an easy life, pray for the strength to endure a difficult one.",
|
||||
zh: "不要祈祷生活安逸,要祈祷有力量去忍受艰难的生活。",
|
||||
},
|
||||
{
|
||||
en: "A pessimist sees the difficulty in every opportunity; an optimist sees the opportunity in every difficulty.",
|
||||
zh: "悲观者在每个机会中都看到困难;乐观者在每个困难中都看到机会。",
|
||||
},
|
||||
{
|
||||
en: "It's not what happens to you, but how you react to it that matters.",
|
||||
zh: "重要的不是发生在你身上的事,而是你如何应对它。",
|
||||
},
|
||||
{
|
||||
en: "To love oneself is the beginning of a lifelong romance.",
|
||||
zh: "爱自己是终身浪漫的开始。",
|
||||
},
|
||||
{
|
||||
en: "Love is composed of a single soul inhabiting two bodies.",
|
||||
zh: "爱是栖息于两个身体中的同一个灵魂。",
|
||||
},
|
||||
{
|
||||
en: "Man is the measure of all things.",
|
||||
zh: "人是万物的尺度。",
|
||||
},
|
||||
{
|
||||
en: "The best and most beautiful things in this world cannot be seen or even heard, but must be felt with the heart.",
|
||||
zh: "世界上最好最美的东西是看不见也听不见的,必须用心去感受。",
|
||||
},
|
||||
{
|
||||
en: "Where there is love there is life.",
|
||||
zh: "有爱的地方就有生命。",
|
||||
},
|
||||
{
|
||||
en: "If you want to be loved, be lovable.",
|
||||
zh: "如果你想被爱,就要变得可爱。",
|
||||
},
|
||||
{
|
||||
en: "We are all in the gutter, but some of us are looking at the stars.",
|
||||
zh: "我们都身处沟渠,但仍有人仰望星空。",
|
||||
},
|
||||
{
|
||||
en: "The only thing we have to fear is fear itself.",
|
||||
zh: "我们唯一需要恐惧的就是恐惧本身。",
|
||||
},
|
||||
{
|
||||
en: "Be kind, for everyone you meet is fighting a hard battle.",
|
||||
zh: "要友善,因为你遇到的每个人都在打一场艰苦的战斗。",
|
||||
},
|
||||
{
|
||||
en: "Man is born free, and everywhere he is in chains.",
|
||||
zh: "人生而自由,却无往不在枷锁之中。",
|
||||
},
|
||||
{
|
||||
en: "We love the things we love for what they are.",
|
||||
zh: "我们爱我们所爱之物,只因它们本来的样子。",
|
||||
},
|
||||
{
|
||||
en: "Darkness cannot drive out darkness; only light can do that. Hate cannot drive out hate; only love can do that.",
|
||||
zh: "黑暗无法驱逐黑暗,只有光明可以;仇恨无法驱逐仇恨,只有爱可以。",
|
||||
},
|
||||
{
|
||||
en: "An eye for an eye only ends up making the whole world blind.",
|
||||
zh: "以眼还眼,只会让整个世界都盲目。",
|
||||
},
|
||||
{
|
||||
en: "Hell is other people.",
|
||||
zh: "他人即地狱。",
|
||||
},
|
||||
{
|
||||
en: "You will not be punished for your anger, you will be punished by your anger.",
|
||||
zh: "你不会因为你的愤怒而受到惩罚,你会被你的愤怒所惩罚。",
|
||||
},
|
||||
{
|
||||
en: "To err is human, to forgive divine.",
|
||||
zh: "犯错是人性,宽恕是神性。",
|
||||
},
|
||||
{
|
||||
en: "Man is the only creature who refuses to be what he is.",
|
||||
zh: "人是唯一拒绝承认自己本质的生物。",
|
||||
},
|
||||
{
|
||||
en: "Beauty is in the eye of the beholder.",
|
||||
zh: "情人眼里出西施。",
|
||||
},
|
||||
{
|
||||
en: "All that we see or seem is but a dream within a dream.",
|
||||
zh: "我们所见所感,皆如梦中之梦。",
|
||||
},
|
||||
{
|
||||
en: "Everything you can imagine is real.",
|
||||
zh: "你能想象的一切都是真实的。",
|
||||
},
|
||||
{
|
||||
en: "The map is not the territory.",
|
||||
zh: "地图并非领土。",
|
||||
},
|
||||
{
|
||||
en: "We don't see things as they are, we see them as we are.",
|
||||
zh: "我们看到的不是事物的原貌,而是我们自己的样子。",
|
||||
},
|
||||
{
|
||||
en: "There are two ways to be fooled. One is to believe what isn't true; the other is to refuse to believe what is true.",
|
||||
zh: "被愚弄有两种方式。一种是相信不真实的东西;另一种是拒绝相信真实的东西。",
|
||||
},
|
||||
{
|
||||
en: "Simplicity is the ultimate sophistication.",
|
||||
zh: "简约是极致的复杂。",
|
||||
},
|
||||
{
|
||||
en: "The truth will set you free.",
|
||||
zh: "真相将使你自由。",
|
||||
},
|
||||
{
|
||||
en: "Reality is merely an illusion, albeit a very persistent one.",
|
||||
zh: "现实只是一种幻觉,尽管是一种非常持久的幻觉。",
|
||||
},
|
||||
{
|
||||
en: "What is rational is actual and what is actual is rational.",
|
||||
zh: "凡是合乎理性的东西都是现实的,凡是现实的东西都是合乎理性的。",
|
||||
},
|
||||
{
|
||||
en: "Truth is like the sun. You can shut it out for a time, but it ain't goin' away.",
|
||||
zh: "真相就像太阳。你可以暂时将它遮住,但它不会消失。",
|
||||
},
|
||||
{
|
||||
en: "Everything we hear is an opinion, not a fact. Everything we see is a perspective, not the truth.",
|
||||
zh: "我们听到的一切都只是观点,而非事实。我们看到的一切都只是视角,而非真相。",
|
||||
},
|
||||
{
|
||||
en: "There is no truth. There is only perception.",
|
||||
zh: "没有真相,只有认知。",
|
||||
},
|
||||
{
|
||||
en: "If you look deep enough into anything, you will find mathematics.",
|
||||
zh: "如果你对任何事物看得足够深入,你都会发现数学。",
|
||||
},
|
||||
{
|
||||
en: "The medium is the message.",
|
||||
zh: "媒介即信息。",
|
||||
},
|
||||
{
|
||||
en: "Nothing is true, everything is permitted.",
|
||||
zh: "没有什么是真实的,一切都被允许。",
|
||||
},
|
||||
{
|
||||
en: "We are what we believe we are.",
|
||||
zh: "我们相信自己是什么,我们就是什么。",
|
||||
},
|
||||
{
|
||||
en: "Yesterday is history, tomorrow is a mystery, but today is a gift. That is why it is called the present.",
|
||||
zh: "昨天是历史,明天是谜团,但今天是礼物。这就是为什么它被称为‘现在’(Present)。",
|
||||
},
|
||||
{
|
||||
en: "Time is money.",
|
||||
zh: "时间就是金钱。",
|
||||
},
|
||||
{
|
||||
en: "The only thing necessary for the triumph of evil is for good men to do nothing.",
|
||||
zh: "邪恶得逞的唯一条件是好人袖手旁观。",
|
||||
},
|
||||
{
|
||||
en: "Carpe diem.",
|
||||
zh: "活在当下。",
|
||||
},
|
||||
{
|
||||
en: "Do not dwell in the past, do not dream of the future, concentrate the mind on the present moment.",
|
||||
zh: "不要沉湎于过去,不要幻想未来,集中精神活在当下。",
|
||||
},
|
||||
{
|
||||
en: "The best time to plant a tree was 20 years ago. The second best time is now.",
|
||||
zh: "种树的最佳时机是20年前。其次是现在。",
|
||||
},
|
||||
{
|
||||
en: "Action speaks louder than words.",
|
||||
zh: "事实胜于雄辩。",
|
||||
},
|
||||
{
|
||||
en: "Honesty is the first chapter in the book of wisdom.",
|
||||
zh: "诚实是智慧之书的第一章。",
|
||||
},
|
||||
{
|
||||
en: "Two things are infinite: the universe and human stupidity; and I'm not sure about the universe.",
|
||||
zh: "有两样东西是无限的:宇宙和人类的愚蠢;而且我不太确定宇宙是否无限。",
|
||||
},
|
||||
{
|
||||
en: "You cannot step twice into the same river.",
|
||||
zh: "人不能两次踏进同一条河流。",
|
||||
},
|
||||
{
|
||||
en: "The future belongs to those who believe in the beauty of their dreams.",
|
||||
zh: "未来属于那些相信梦想之美的人。",
|
||||
},
|
||||
{
|
||||
en: "Procrastination is the thief of time.",
|
||||
zh: "拖延是时间的大敌。",
|
||||
},
|
||||
{
|
||||
en: "An investment in knowledge pays the best interest.",
|
||||
zh: "投资知识,收益最佳。",
|
||||
},
|
||||
{
|
||||
en: "I have not failed. I've just found 10,000 ways that won't work.",
|
||||
zh: "我没有失败。我只是找到了一万种行不通的方法。",
|
||||
},
|
||||
{
|
||||
en: "That which is done, is done.",
|
||||
zh: "木已成舟。",
|
||||
},
|
||||
];
|
||||
|
||||
export function getRandomQuote() {
|
||||
const randomIndex = Math.floor(Math.random() * quotes.length);
|
||||
return quotes[randomIndex];
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { OPT_TRANS_MICROSOFT } from "./api";
|
||||
import { OPT_STYLE_NONE } from "./styles";
|
||||
|
||||
export const GLOBAL_KEY = "*";
|
||||
export const REMAIN_KEY = "-";
|
||||
@@ -10,48 +11,6 @@ export const DEFAULT_TRANS_TAG = "font";
|
||||
export const DEFAULT_SELECT_STYLE =
|
||||
"-webkit-line-clamp: unset; max-height: none; height: auto;";
|
||||
|
||||
export const OPT_STYLE_NONE = "style_none"; // 无
|
||||
export const OPT_STYLE_LINE = "under_line"; // 下划线
|
||||
export const OPT_STYLE_DOTLINE = "dot_line"; // 点状线
|
||||
export const OPT_STYLE_DASHLINE = "dash_line"; // 虚线
|
||||
export const OPT_STYLE_DASHBOX = "dash_box"; // 虚线框
|
||||
export const OPT_STYLE_WAVYLINE = "wavy_line"; // 波浪线
|
||||
export const OPT_STYLE_MARKER = "marker"; // 马克笔
|
||||
export const OPT_STYLE_GRADIENT_MARKER = "gradient_marker"; // 渐变马克笔
|
||||
export const OPT_STYLE_FUZZY = "fuzzy"; // 模糊
|
||||
export const OPT_STYLE_HIGHLIGHT = "highlight"; // 高亮
|
||||
export const OPT_STYLE_BLOCKQUOTE = "blockquote"; // 引用
|
||||
export const OPT_STYLE_GRADIENT = "gradient"; // 渐变
|
||||
export const OPT_STYLE_BLINK = "blink"; // 闪现
|
||||
export const OPT_STYLE_GLOW = "glow"; // 发光
|
||||
export const OPT_STYLE_DIY = "diy_style"; // 自定义样式
|
||||
export const OPT_STYLE_ALL = [
|
||||
OPT_STYLE_NONE,
|
||||
OPT_STYLE_LINE,
|
||||
OPT_STYLE_DOTLINE,
|
||||
OPT_STYLE_DASHLINE,
|
||||
OPT_STYLE_WAVYLINE,
|
||||
OPT_STYLE_DASHBOX,
|
||||
OPT_STYLE_MARKER,
|
||||
OPT_STYLE_GRADIENT_MARKER,
|
||||
OPT_STYLE_FUZZY,
|
||||
OPT_STYLE_HIGHLIGHT,
|
||||
OPT_STYLE_BLOCKQUOTE,
|
||||
OPT_STYLE_GRADIENT,
|
||||
OPT_STYLE_BLINK,
|
||||
OPT_STYLE_GLOW,
|
||||
OPT_STYLE_DIY,
|
||||
];
|
||||
export const OPT_STYLE_USE_COLOR = [
|
||||
OPT_STYLE_LINE,
|
||||
OPT_STYLE_DOTLINE,
|
||||
OPT_STYLE_DASHLINE,
|
||||
OPT_STYLE_DASHBOX,
|
||||
OPT_STYLE_WAVYLINE,
|
||||
OPT_STYLE_HIGHLIGHT,
|
||||
OPT_STYLE_BLOCKQUOTE,
|
||||
];
|
||||
|
||||
export const OPT_TIMING_PAGESCROLL = "mk_pagescroll"; // 滚动加载翻译
|
||||
export const OPT_TIMING_PAGEOPEN = "mk_pageopen"; // 直接翻译到底
|
||||
export const OPT_TIMING_MOUSEOVER = "mk_mouseover";
|
||||
@@ -85,19 +44,6 @@ export const OPT_HIGHLIGHT_WORDS_ALL = [
|
||||
OPT_HIGHLIGHT_WORDS_AFTERTRANS,
|
||||
];
|
||||
|
||||
export const DEFAULT_DIY_STYLE = `color: #333;
|
||||
background: linear-gradient(
|
||||
45deg,
|
||||
LightGreen 20%,
|
||||
LightPink 20% 40%,
|
||||
LightSalmon 40% 60%,
|
||||
LightSeaGreen 60% 80%,
|
||||
LightSkyBlue 80%
|
||||
);
|
||||
&:hover {
|
||||
color: #111;
|
||||
};`;
|
||||
|
||||
export const DEFAULT_SELECTOR =
|
||||
"h1, h2, h3, h4, h5, h6, li, p, dd, blockquote, figcaption, label, legend";
|
||||
export const DEFAULT_IGNORE_SELECTOR = "button, footer, pre, mark, nav";
|
||||
@@ -113,8 +59,8 @@ export const DEFAULT_RULE = {
|
||||
toLang: GLOBAL_KEY, // 目标语言
|
||||
textStyle: GLOBAL_KEY, // 译文样式
|
||||
transOpen: GLOBAL_KEY, // 开启翻译
|
||||
bgColor: "", // 译文颜色
|
||||
textDiyStyle: "", // 自定义译文样式
|
||||
// bgColor: "", // 译文颜色 (作废)
|
||||
// textDiyStyle: "", // 自定义译文样式 (作废)
|
||||
textExtStyle: "", // 译文附加样式
|
||||
termsStyle: "", // 专业术语样式
|
||||
highlightStyle: "", // 高亮词汇样式
|
||||
@@ -157,8 +103,8 @@ export const GLOBLA_RULE = {
|
||||
toLang: "zh-CN", // 目标语言
|
||||
textStyle: OPT_STYLE_NONE, // 译文样式
|
||||
transOpen: "false", // 开启翻译
|
||||
bgColor: DEFAULT_COLOR, // 译文颜色
|
||||
textDiyStyle: DEFAULT_DIY_STYLE, // 自定义译文样式
|
||||
// bgColor: DEFAULT_COLOR, // 译文颜色 (作废)
|
||||
// textDiyStyle: DEFAULT_DIY_STYLE, // 自定义译文样式 (作废)
|
||||
textExtStyle: "", // 译文附加样式
|
||||
termsStyle: "font-weight: bold;", // 专业术语样式
|
||||
highlightStyle: "color: red;", // 高亮词汇样式
|
||||
@@ -191,16 +137,6 @@ export const GLOBLA_RULE = {
|
||||
|
||||
export const DEFAULT_RULES = [GLOBLA_RULE];
|
||||
|
||||
export const DEFAULT_OW_RULE = {
|
||||
apiSlug: REMAIN_KEY,
|
||||
fromLang: REMAIN_KEY,
|
||||
toLang: REMAIN_KEY,
|
||||
textStyle: REMAIN_KEY,
|
||||
transOpen: REMAIN_KEY,
|
||||
bgColor: "",
|
||||
textDiyStyle: DEFAULT_DIY_STYLE,
|
||||
};
|
||||
|
||||
// todo: 校验几个内置规则
|
||||
const RULES_MAP = {
|
||||
// "www.google.com/search": {
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
OPT_TRANS_MICROSOFT,
|
||||
DEFAULT_API_LIST,
|
||||
} from "./api";
|
||||
import { DEFAULT_CUSTOM_STYLES } from "./styles";
|
||||
|
||||
// 默认快捷键
|
||||
export const OPT_SHORTCUT_TRANSLATE = "toggleTranslate";
|
||||
@@ -183,4 +184,5 @@ export const DEFAULT_SETTING = {
|
||||
subtitleSetting: DEFAULT_SUBTITLE_SETTING, // 字幕设置
|
||||
logLevel: LogLevel.INFO.value, // 日志级别
|
||||
rootMargin: 500, // 提前触发翻译
|
||||
customStyles: DEFAULT_CUSTOM_STYLES, // 自定义样式列表
|
||||
};
|
||||
|
||||
40
src/config/styles.js
Normal file
40
src/config/styles.js
Normal file
@@ -0,0 +1,40 @@
|
||||
export const OPT_STYLE_NONE = "style_none"; // 无
|
||||
export const OPT_STYLE_LINE = "under_line"; // 下划线
|
||||
export const OPT_STYLE_DOTLINE = "dot_line"; // 点状线
|
||||
export const OPT_STYLE_DASHLINE = "dash_line"; // 虚线
|
||||
export const OPT_STYLE_DASHBOX = "dash_box"; // 虚线框
|
||||
export const OPT_STYLE_WAVYLINE = "wavy_line"; // 波浪线
|
||||
export const OPT_STYLE_MARKER = "marker"; // 马克笔
|
||||
export const OPT_STYLE_GRADIENT_MARKER = "gradient_marker"; // 渐变马克笔
|
||||
export const OPT_STYLE_FUZZY = "fuzzy"; // 模糊
|
||||
export const OPT_STYLE_HIGHLIGHT = "highlight"; // 高亮
|
||||
export const OPT_STYLE_BLOCKQUOTE = "blockquote"; // 引用
|
||||
export const OPT_STYLE_GRADIENT = "gradient"; // 渐变
|
||||
export const OPT_STYLE_BLINK = "blink"; // 闪现
|
||||
export const OPT_STYLE_GLOW = "glow"; // 发光
|
||||
export const OPT_STYLE_COLORFUL = "colorful"; // 多彩
|
||||
export const OPT_STYLE_ALL = [
|
||||
OPT_STYLE_NONE,
|
||||
OPT_STYLE_LINE,
|
||||
OPT_STYLE_DOTLINE,
|
||||
OPT_STYLE_DASHLINE,
|
||||
OPT_STYLE_WAVYLINE,
|
||||
OPT_STYLE_DASHBOX,
|
||||
OPT_STYLE_MARKER,
|
||||
OPT_STYLE_GRADIENT_MARKER,
|
||||
OPT_STYLE_FUZZY,
|
||||
OPT_STYLE_HIGHLIGHT,
|
||||
OPT_STYLE_BLOCKQUOTE,
|
||||
OPT_STYLE_GRADIENT,
|
||||
OPT_STYLE_BLINK,
|
||||
OPT_STYLE_GLOW,
|
||||
OPT_STYLE_COLORFUL,
|
||||
];
|
||||
|
||||
export const DEFAULT_CUSTOM_STYLES = [
|
||||
{
|
||||
styleSlug: "custom",
|
||||
styleName: "Custom Style",
|
||||
styleCode: `color: #209CEE;`,
|
||||
},
|
||||
];
|
||||
84
src/hooks/CustomStyles.js
Normal file
84
src/hooks/CustomStyles.js
Normal file
@@ -0,0 +1,84 @@
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { useSetting } from "./Setting";
|
||||
import { DEFAULT_CUSTOM_STYLES, OPT_STYLE_ALL } from "../config/styles";
|
||||
import { builtinStylesMap } from "../libs/style";
|
||||
import { useI18n } from "./I18n";
|
||||
|
||||
function useStyleState() {
|
||||
const { setting, updateSetting } = useSetting();
|
||||
const customStyles = setting?.customStyles || [];
|
||||
|
||||
return { customStyles, updateSetting };
|
||||
}
|
||||
|
||||
export function useStyleList() {
|
||||
const { customStyles, updateSetting } = useStyleState();
|
||||
|
||||
const addStyle = useCallback(() => {
|
||||
const defaultStyle = DEFAULT_CUSTOM_STYLES[0];
|
||||
const uuid = crypto.randomUUID();
|
||||
const styleSlug = `custom_${crypto.randomUUID()}`;
|
||||
const styleName = `Style_${uuid.slice(0, 8)}`;
|
||||
const newStyle = {
|
||||
...defaultStyle,
|
||||
styleSlug,
|
||||
styleName,
|
||||
};
|
||||
updateSetting((prev) => ({
|
||||
...prev,
|
||||
customStyles: [...(prev?.customStyles || []), newStyle],
|
||||
}));
|
||||
}, [updateSetting]);
|
||||
|
||||
const deleteStyle = useCallback(
|
||||
(styleSlug) => {
|
||||
updateSetting((prev) => ({
|
||||
...prev,
|
||||
customStyles: (prev?.customStyles || []).filter(
|
||||
(item) => item.styleSlug !== styleSlug
|
||||
),
|
||||
}));
|
||||
},
|
||||
[updateSetting]
|
||||
);
|
||||
|
||||
const updateStyle = useCallback(
|
||||
(styleSlug, updateData) => {
|
||||
updateSetting((prev) => ({
|
||||
...prev,
|
||||
customStyles: (prev?.customStyles || []).map((item) =>
|
||||
item.styleSlug === styleSlug ? { ...item, ...updateData } : item
|
||||
),
|
||||
}));
|
||||
},
|
||||
[updateSetting]
|
||||
);
|
||||
|
||||
return {
|
||||
customStyles,
|
||||
addStyle,
|
||||
deleteStyle,
|
||||
updateStyle,
|
||||
};
|
||||
}
|
||||
|
||||
export function useAllTextStyles() {
|
||||
const { customStyles } = useStyleList();
|
||||
const i18n = useI18n();
|
||||
|
||||
const builtinStyles = useMemo(
|
||||
() =>
|
||||
OPT_STYLE_ALL.map((styleSlug) => ({
|
||||
styleSlug,
|
||||
styleName: i18n(styleSlug),
|
||||
styleCode: builtinStylesMap[styleSlug] || "",
|
||||
})),
|
||||
[i18n]
|
||||
);
|
||||
|
||||
const allTextStyles = useMemo(() => {
|
||||
return [...builtinStyles, ...customStyles];
|
||||
}, [builtinStyles, customStyles]);
|
||||
|
||||
return { builtinStyles, customStyles, allTextStyles };
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DEFAULT_SUBRULES_LIST, DEFAULT_OW_RULE } from "../config";
|
||||
import { DEFAULT_SUBRULES_LIST } from "../config";
|
||||
import { useSetting } from "./Setting";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { loadOrFetchSubRules } from "../libs/subRules";
|
||||
@@ -78,15 +78,3 @@ export function useSubRules() {
|
||||
loading,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 覆写订阅规则
|
||||
* @returns
|
||||
*/
|
||||
export function useOwSubRule() {
|
||||
const { setting, updateChild } = useSetting();
|
||||
const owSubrule = setting?.owSubrule || DEFAULT_OW_RULE;
|
||||
const updateOwSubrule = updateChild("owSubrule");
|
||||
|
||||
return { owSubrule, updateOwSubrule };
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { matchValue, type, isMatch } from "./utils";
|
||||
import {
|
||||
GLOBAL_KEY,
|
||||
OPT_STYLE_ALL,
|
||||
OPT_LANGS_FROM,
|
||||
OPT_LANGS_TO,
|
||||
// OPT_TIMING_ALL,
|
||||
DEFAULT_RULE,
|
||||
GLOBLA_RULE,
|
||||
OPT_SPLIT_PARAGRAPH_ALL,
|
||||
@@ -13,7 +11,6 @@ import {
|
||||
import { loadOrFetchSubRules } from "./subRules";
|
||||
import { getRulesWithDefault, setRules } from "./storage";
|
||||
import { trySyncRules } from "./sync";
|
||||
// import { FIXER_ALL } from "./webfix";
|
||||
import { kissLog } from "./log";
|
||||
|
||||
/**
|
||||
@@ -62,7 +59,6 @@ export const matchRule = async (href, { injectRules, subrulesList }) => {
|
||||
"grandStyle",
|
||||
"injectJs",
|
||||
// "injectCss",
|
||||
// "fixerSelector",
|
||||
"transStartHook",
|
||||
"transEndHook",
|
||||
// "transRemoveHook",
|
||||
@@ -78,16 +74,14 @@ export const matchRule = async (href, { injectRules, subrulesList }) => {
|
||||
"toLang",
|
||||
"transOpen",
|
||||
"transOnly",
|
||||
// "transTiming",
|
||||
"autoScan",
|
||||
"hasRichText",
|
||||
"hasShadowroot",
|
||||
"transTag",
|
||||
"transTitle",
|
||||
// "detectRemote",
|
||||
// "fixerFunc",
|
||||
"splitParagraph",
|
||||
"highlightWords",
|
||||
"textStyle",
|
||||
].forEach((key) => {
|
||||
if (!rule[key] || rule[key] === GLOBAL_KEY) {
|
||||
rule[key] = globalRule[key];
|
||||
@@ -100,17 +94,6 @@ export const matchRule = async (href, { injectRules, subrulesList }) => {
|
||||
}
|
||||
});
|
||||
|
||||
// if (!rule.skipLangs || rule.skipLangs.length === 0) {
|
||||
// rule.skipLangs = globalRule.skipLangs;
|
||||
// }
|
||||
if (!rule.textStyle || rule.textStyle === GLOBAL_KEY) {
|
||||
rule.textStyle = globalRule.textStyle;
|
||||
rule.bgColor = globalRule.bgColor;
|
||||
rule.textDiyStyle = globalRule.textDiyStyle;
|
||||
} else {
|
||||
rule.bgColor = rule.bgColor?.trim() || globalRule.bgColor;
|
||||
rule.textDiyStyle = rule.textDiyStyle?.trim() || globalRule.textDiyStyle;
|
||||
}
|
||||
|
||||
return rule;
|
||||
};
|
||||
@@ -162,19 +145,12 @@ export const checkRules = (rules) => {
|
||||
toLang,
|
||||
textStyle,
|
||||
transOpen,
|
||||
bgColor,
|
||||
textDiyStyle,
|
||||
transOnly,
|
||||
autoScan,
|
||||
hasRichText,
|
||||
hasShadowroot,
|
||||
// transTiming,
|
||||
transTag,
|
||||
transTitle,
|
||||
// detectRemote,
|
||||
// skipLangs,
|
||||
// fixerSelector,
|
||||
// fixerFunc,
|
||||
transStartHook,
|
||||
transEndHook,
|
||||
// transRemoveHook,
|
||||
@@ -197,31 +173,28 @@ export const checkRules = (rules) => {
|
||||
grandStyle: type(grandStyle) === "string" ? grandStyle : "",
|
||||
injectJs: type(injectJs) === "string" ? injectJs : "",
|
||||
// injectCss: type(injectCss) === "string" ? injectCss : "",
|
||||
bgColor: type(bgColor) === "string" ? bgColor : "",
|
||||
textDiyStyle: type(textDiyStyle) === "string" ? textDiyStyle : "",
|
||||
apiSlug:
|
||||
type(apiSlug) === "string" && apiSlug.trim() !== ""
|
||||
? apiSlug.trim()
|
||||
: GLOBAL_KEY,
|
||||
fromLang: matchValue([GLOBAL_KEY, ...fromLangs], fromLang),
|
||||
toLang: matchValue([GLOBAL_KEY, ...toLangs], toLang),
|
||||
textStyle: matchValue([GLOBAL_KEY, ...OPT_STYLE_ALL], textStyle),
|
||||
// textStyle: matchValue([GLOBAL_KEY, ...OPT_STYLE_ALL], textStyle),
|
||||
textStyle:
|
||||
type(textStyle) === "string" && textStyle.trim() !== ""
|
||||
? textStyle.trim()
|
||||
: GLOBAL_KEY,
|
||||
transOpen: matchValue([GLOBAL_KEY, "true", "false"], transOpen),
|
||||
transOnly: matchValue([GLOBAL_KEY, "true", "false"], transOnly),
|
||||
autoScan: matchValue([GLOBAL_KEY, "true", "false"], autoScan),
|
||||
hasRichText: matchValue([GLOBAL_KEY, "true", "false"], hasRichText),
|
||||
hasShadowroot: matchValue([GLOBAL_KEY, "true", "false"], hasShadowroot),
|
||||
// transTiming: matchValue([GLOBAL_KEY, ...OPT_TIMING_ALL], transTiming),
|
||||
transTag: matchValue([GLOBAL_KEY, "span", "font"], transTag),
|
||||
transTitle: matchValue([GLOBAL_KEY, "true", "false"], transTitle),
|
||||
// detectRemote: matchValue([GLOBAL_KEY, "true", "false"], detectRemote),
|
||||
// skipLangs: type(skipLangs) === "array" ? skipLangs : [],
|
||||
// fixerSelector: type(fixerSelector) === "string" ? fixerSelector : "",
|
||||
transStartHook: type(transStartHook) === "string" ? transStartHook : "",
|
||||
transEndHook: type(transEndHook) === "string" ? transEndHook : "",
|
||||
// transRemoveHook:
|
||||
// type(transRemoveHook) === "string" ? transRemoveHook : "",
|
||||
// fixerFunc: matchValue([GLOBAL_KEY, ...FIXER_ALL], fixerFunc),
|
||||
splitParagraph: matchValue(
|
||||
[GLOBAL_KEY, ...OPT_SPLIT_PARAGRAPH_ALL],
|
||||
splitParagraph
|
||||
|
||||
@@ -12,8 +12,7 @@ import {
|
||||
OPT_STYLE_GRADIENT,
|
||||
OPT_STYLE_BLINK,
|
||||
OPT_STYLE_GLOW,
|
||||
OPT_STYLE_DIY,
|
||||
DEFAULT_DIY_STYLE,
|
||||
OPT_STYLE_COLORFUL,
|
||||
DEFAULT_COLOR,
|
||||
OPT_STYLE_MARKER,
|
||||
OPT_STYLE_GRADIENT_MARKER,
|
||||
@@ -69,10 +68,7 @@ const genLineStyle = (style, color) => `
|
||||
}
|
||||
`;
|
||||
|
||||
const genStyles = ({
|
||||
textDiyStyle = DEFAULT_DIY_STYLE,
|
||||
color = DEFAULT_COLOR,
|
||||
} = {}) => ({
|
||||
const genBuiltinStyles = (color = DEFAULT_COLOR) => ({
|
||||
// 无样式
|
||||
[OPT_STYLE_NONE]: ``,
|
||||
// 下划线
|
||||
@@ -148,14 +144,29 @@ const genStyles = ({
|
||||
[OPT_STYLE_GLOW]: `
|
||||
animation: ${glow} 2s ease-in-out infinite alternate;
|
||||
`,
|
||||
// 自定义
|
||||
[OPT_STYLE_DIY]: `
|
||||
${textDiyStyle}
|
||||
// 多彩
|
||||
[OPT_STYLE_COLORFUL]: `
|
||||
color: #333;
|
||||
background: linear-gradient(
|
||||
45deg,
|
||||
LightGreen 20%,
|
||||
LightPink 20% 40%,
|
||||
LightSalmon 40% 60%,
|
||||
LightSeaGreen 60% 80%,
|
||||
LightSkyBlue 80%
|
||||
);
|
||||
&:hover {
|
||||
color: #111;
|
||||
};
|
||||
`,
|
||||
});
|
||||
|
||||
export const genTextClass = ({ textDiyStyle, bgColor }) => {
|
||||
const styles = genStyles({ textDiyStyle, color: bgColor || DEFAULT_COLOR });
|
||||
export const genTextClass = (customStyles = []) => {
|
||||
const styles = genBuiltinStyles();
|
||||
customStyles.forEach((style) => {
|
||||
styles[style.styleSlug] = style.styleCode;
|
||||
});
|
||||
|
||||
const textClass = {};
|
||||
let textStyles = "";
|
||||
Object.entries(styles).forEach(([k, v]) => {
|
||||
@@ -173,4 +184,4 @@ export const genTextClass = ({ textDiyStyle, bgColor }) => {
|
||||
return [textClass, textStyles];
|
||||
};
|
||||
|
||||
export const defaultStyles = genStyles();
|
||||
export const builtinStylesMap = genBuiltinStyles();
|
||||
|
||||
@@ -466,7 +466,7 @@ export class Translator {
|
||||
|
||||
// 创建样式
|
||||
#createTextStyles() {
|
||||
const [textClass, textStyles] = genTextClass({ ...this.#rule });
|
||||
const [textClass, textStyles] = genTextClass(this.#setting.customStyles);
|
||||
const textSheet = new CSSStyleSheet();
|
||||
textSheet.replaceSync(textStyles);
|
||||
this.#textClass = textClass;
|
||||
@@ -1187,7 +1187,7 @@ export class Translator {
|
||||
}
|
||||
|
||||
const inner = document.createElement(transTag);
|
||||
inner.className = `${Translator.KISS_CLASS.inner} ${this.#textClass[textStyle]}`;
|
||||
inner.className = `${Translator.KISS_CLASS.inner} ${this.#textClass[textStyle] || ""}`;
|
||||
if (textExtStyle?.trim()) {
|
||||
inner.style.cssText = textExtStyle; // 附加内联样式
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import SelectAllIcon from "@mui/icons-material/SelectAll";
|
||||
import EventNoteIcon from "@mui/icons-material/EventNote";
|
||||
import MouseIcon from "@mui/icons-material/Mouse";
|
||||
import SubtitlesIcon from "@mui/icons-material/Subtitles";
|
||||
import FormatColorText from "@mui/icons-material/FormatColorText";
|
||||
|
||||
function LinkItem({ label, url, icon }) {
|
||||
const match = useMatch(url);
|
||||
@@ -42,6 +43,24 @@ export default function Navigator(props) {
|
||||
url: "/rules",
|
||||
icon: <DesignServicesIcon />,
|
||||
},
|
||||
{
|
||||
id: "apis_setting",
|
||||
label: i18n("apis_setting"),
|
||||
url: "/apis",
|
||||
icon: <ApiIcon />,
|
||||
},
|
||||
{
|
||||
id: "styles_setting",
|
||||
label: i18n("styles_setting"),
|
||||
url: "/styles",
|
||||
icon: <FormatColorText />,
|
||||
},
|
||||
{
|
||||
id: "sync",
|
||||
label: i18n("sync_setting"),
|
||||
url: "/sync",
|
||||
icon: <SyncIcon />,
|
||||
},
|
||||
{
|
||||
id: "input_translate",
|
||||
label: i18n("input_translate"),
|
||||
@@ -66,18 +85,6 @@ export default function Navigator(props) {
|
||||
url: "/subtitle",
|
||||
icon: <SubtitlesIcon />,
|
||||
},
|
||||
{
|
||||
id: "apis_setting",
|
||||
label: i18n("apis_setting"),
|
||||
url: "/apis",
|
||||
icon: <ApiIcon />,
|
||||
},
|
||||
{
|
||||
id: "sync",
|
||||
label: i18n("sync_setting"),
|
||||
url: "/sync",
|
||||
icon: <SyncIcon />,
|
||||
},
|
||||
{
|
||||
id: "words",
|
||||
label: i18n("favorite_words"),
|
||||
|
||||
@@ -1,176 +0,0 @@
|
||||
import Box from "@mui/material/Box";
|
||||
import Stack from "@mui/material/Stack";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import {
|
||||
GLOBAL_KEY,
|
||||
REMAIN_KEY,
|
||||
OPT_LANGS_FROM,
|
||||
OPT_LANGS_TO,
|
||||
OPT_STYLE_ALL,
|
||||
OPT_STYLE_DIY,
|
||||
OPT_STYLE_USE_COLOR,
|
||||
} from "../../config";
|
||||
import { useI18n } from "../../hooks/I18n";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import { useOwSubRule } from "../../hooks/SubRules";
|
||||
import { useApiList } from "../../hooks/Api";
|
||||
|
||||
export default function OwSubRule() {
|
||||
const i18n = useI18n();
|
||||
const { owSubrule, updateOwSubrule } = useOwSubRule();
|
||||
const { enabledApis } = useApiList();
|
||||
|
||||
const handleChange = (e) => {
|
||||
e.preventDefault();
|
||||
const { name, value } = e.target;
|
||||
updateOwSubrule({ [name]: value });
|
||||
};
|
||||
|
||||
const {
|
||||
apiSlug,
|
||||
fromLang,
|
||||
toLang,
|
||||
textStyle,
|
||||
transOpen,
|
||||
bgColor,
|
||||
textDiyStyle,
|
||||
} = owSubrule;
|
||||
|
||||
const RemainItem = (
|
||||
<MenuItem key={REMAIN_KEY} value={REMAIN_KEY}>
|
||||
{i18n("remain_unchanged")}
|
||||
</MenuItem>
|
||||
);
|
||||
|
||||
const GlobalItem = (
|
||||
<MenuItem key={GLOBAL_KEY} value={GLOBAL_KEY}>
|
||||
{GLOBAL_KEY}
|
||||
</MenuItem>
|
||||
);
|
||||
|
||||
return (
|
||||
<Stack spacing={2}>
|
||||
<Box>
|
||||
<Grid container spacing={2} columns={12}>
|
||||
<Grid item xs={12} sm={6} md={3} lg={2}>
|
||||
<TextField
|
||||
select
|
||||
size="small"
|
||||
fullWidth
|
||||
name="transOpen"
|
||||
value={transOpen}
|
||||
label={i18n("translate_switch")}
|
||||
onChange={handleChange}
|
||||
>
|
||||
{RemainItem}
|
||||
{GlobalItem}
|
||||
<MenuItem value={"true"}>{i18n("default_enabled")}</MenuItem>
|
||||
<MenuItem value={"false"}>{i18n("default_disabled")}</MenuItem>
|
||||
</TextField>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6} md={3} lg={2}>
|
||||
<TextField
|
||||
select
|
||||
size="small"
|
||||
fullWidth
|
||||
name="apiSlug"
|
||||
value={apiSlug}
|
||||
label={i18n("translate_service")}
|
||||
onChange={handleChange}
|
||||
>
|
||||
{RemainItem}
|
||||
{GlobalItem}
|
||||
{enabledApis.map((api) => (
|
||||
<MenuItem key={api.apiSlug} value={api.apiSlug}>
|
||||
{api.apiName}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6} md={3} lg={2}>
|
||||
<TextField
|
||||
select
|
||||
size="small"
|
||||
fullWidth
|
||||
name="fromLang"
|
||||
value={fromLang}
|
||||
label={i18n("from_lang")}
|
||||
onChange={handleChange}
|
||||
>
|
||||
{RemainItem}
|
||||
{GlobalItem}
|
||||
{OPT_LANGS_FROM.map(([lang, name]) => (
|
||||
<MenuItem key={lang} value={lang}>
|
||||
{name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6} md={3} lg={2}>
|
||||
<TextField
|
||||
select
|
||||
size="small"
|
||||
fullWidth
|
||||
name="toLang"
|
||||
value={toLang}
|
||||
label={i18n("to_lang")}
|
||||
onChange={handleChange}
|
||||
>
|
||||
{RemainItem}
|
||||
{GlobalItem}
|
||||
{OPT_LANGS_TO.map(([lang, name]) => (
|
||||
<MenuItem key={lang} value={lang}>
|
||||
{name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6} md={3} lg={2}>
|
||||
<TextField
|
||||
select
|
||||
size="small"
|
||||
fullWidth
|
||||
name="textStyle"
|
||||
value={textStyle}
|
||||
label={i18n("text_style")}
|
||||
onChange={handleChange}
|
||||
>
|
||||
{RemainItem}
|
||||
{GlobalItem}
|
||||
{OPT_STYLE_ALL.map((item) => (
|
||||
<MenuItem key={item} value={item}>
|
||||
{i18n(item)}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
</Grid>
|
||||
{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")}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</Box>
|
||||
|
||||
{textStyle === OPT_STYLE_DIY && (
|
||||
<TextField
|
||||
size="small"
|
||||
label={i18n("diy_style")}
|
||||
helperText={i18n("diy_style_helper")}
|
||||
name="textDiyStyle"
|
||||
value={textDiyStyle}
|
||||
onChange={handleChange}
|
||||
multiline
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
@@ -10,9 +10,6 @@ import {
|
||||
GLOBLA_RULE,
|
||||
OPT_LANGS_FROM,
|
||||
OPT_LANGS_TO,
|
||||
OPT_STYLE_ALL,
|
||||
OPT_STYLE_DIY,
|
||||
// OPT_STYLE_USE_COLOR,
|
||||
URL_KISS_RULES_NEW_ISSUE,
|
||||
OPT_SYNCTYPE_WORKER,
|
||||
DEFAULT_TRANS_TAG,
|
||||
@@ -53,7 +50,6 @@ import {
|
||||
getSyncWithDefault,
|
||||
getRulesOld,
|
||||
} from "../../libs/storage";
|
||||
// import OwSubRule from "./OwSubRule";
|
||||
import ClearAllIcon from "@mui/icons-material/ClearAll";
|
||||
import HelpButton from "./HelpButton";
|
||||
import { useSyncCaches } from "../../hooks/Sync";
|
||||
@@ -68,7 +64,7 @@ import { kissLog } from "../../libs/log";
|
||||
import { useApiList } from "../../hooks/Api";
|
||||
import ShowMoreButton from "./ShowMoreButton";
|
||||
import { useConfirm } from "../../hooks/Confirm";
|
||||
import { defaultStyles } from "../../libs/style";
|
||||
import { useAllTextStyles } from "../../hooks/CustomStyles";
|
||||
|
||||
const calculateInitialValues = (rule) => {
|
||||
const base = rule?.pattern === "*" ? GLOBLA_RULE : DEFAULT_RULE;
|
||||
@@ -87,6 +83,7 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
|
||||
const [formValues, setFormValues] = useState(initialFormValues);
|
||||
const [showMore, setShowMore] = useState(!rules);
|
||||
const { enabledApis } = useApiList();
|
||||
const { allTextStyles } = useAllTextStyles();
|
||||
|
||||
useEffect(() => {
|
||||
const newInitialValues = calculateInitialValues(rule);
|
||||
@@ -115,8 +112,8 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
|
||||
toLang,
|
||||
textStyle,
|
||||
transOpen,
|
||||
bgColor,
|
||||
textDiyStyle,
|
||||
// bgColor,
|
||||
// textDiyStyle,
|
||||
transOnly = "false",
|
||||
autoScan = "true",
|
||||
hasRichText = "true",
|
||||
@@ -140,13 +137,6 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
|
||||
return JSON.stringify(initialFormValues) !== JSON.stringify(formValues);
|
||||
}, [initialFormValues, formValues]);
|
||||
|
||||
const stylesExample = useMemo(() => {
|
||||
return Object.entries(defaultStyles)
|
||||
.filter(([_, v]) => v)
|
||||
.map(([k, v]) => `${i18n(k)}:${v}`)
|
||||
.join("\n");
|
||||
}, [i18n]);
|
||||
|
||||
const hasSamePattern = (str) => {
|
||||
for (const item of rules.list) {
|
||||
if (item.pattern === str && rule?.pattern !== str) {
|
||||
@@ -531,61 +521,16 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
|
||||
onChange={handleChange}
|
||||
>
|
||||
{GlobalItem}
|
||||
{OPT_STYLE_ALL.map((item) => (
|
||||
<MenuItem key={item} value={item}>
|
||||
{i18n(item)}
|
||||
{allTextStyles.map((item) => (
|
||||
<MenuItem key={item.styleSlug} value={item.styleSlug}>
|
||||
{item.styleName}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={12} md={6} lg={3}>
|
||||
<TextField
|
||||
size="small"
|
||||
fullWidth
|
||||
name="bgColor"
|
||||
value={bgColor}
|
||||
label={i18n("bg_color")}
|
||||
disabled={disabled}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
|
||||
{textStyle === OPT_STYLE_DIY && (
|
||||
<TextField
|
||||
size="small"
|
||||
label={i18n("diy_style")}
|
||||
FormHelperTextProps={{
|
||||
component: "div",
|
||||
}}
|
||||
helperText={
|
||||
<Box>
|
||||
<Box component="div">{i18n("default_styles_example")}</Box>
|
||||
<Box
|
||||
component="pre"
|
||||
sx={{
|
||||
overflowX: "auto",
|
||||
height: 200,
|
||||
resize: "vertical",
|
||||
minHeight: 100,
|
||||
margin: 0,
|
||||
// border: "1px solid #ccc",
|
||||
}}
|
||||
>
|
||||
{stylesExample}
|
||||
</Box>
|
||||
</Box>
|
||||
}
|
||||
name="textDiyStyle"
|
||||
value={textDiyStyle}
|
||||
disabled={disabled}
|
||||
onChange={handleChange}
|
||||
maxRows={10}
|
||||
multiline
|
||||
/>
|
||||
)}
|
||||
|
||||
{showMore && (
|
||||
<>
|
||||
<TextField
|
||||
|
||||
220
src/views/Options/StylesSetting.js
Normal file
220
src/views/Options/StylesSetting.js
Normal file
@@ -0,0 +1,220 @@
|
||||
import { useState, useEffect, useMemo } from "react";
|
||||
import Stack from "@mui/material/Stack";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import Button from "@mui/material/Button";
|
||||
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 AddIcon from "@mui/icons-material/Add";
|
||||
import { useConfirm } from "../../hooks/Confirm";
|
||||
import Box from "@mui/material/Box";
|
||||
import { useAllTextStyles, useStyleList } from "../../hooks/CustomStyles";
|
||||
import { css } from "@emotion/css";
|
||||
import { getRandomQuote } from "../../config/quotes";
|
||||
import { useSetting } from "../../hooks/Setting";
|
||||
|
||||
function StyleFields({ customStyle, deleteStyle, updateStyle, isBuiltin }) {
|
||||
const i18n = useI18n();
|
||||
const {
|
||||
setting: { uiLang },
|
||||
} = useSetting();
|
||||
const [formData, setFormData] = useState({});
|
||||
const [isModified, setIsModified] = useState(false);
|
||||
const confirm = useConfirm();
|
||||
|
||||
useEffect(() => {
|
||||
if (customStyle) {
|
||||
setFormData(customStyle);
|
||||
}
|
||||
}, [customStyle]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!customStyle) return;
|
||||
const hasChanged = JSON.stringify(customStyle) !== JSON.stringify(formData);
|
||||
setIsModified(hasChanged);
|
||||
}, [customStyle, formData]);
|
||||
|
||||
const handleChange = (e) => {
|
||||
e.preventDefault();
|
||||
let { name, value } = e.target;
|
||||
|
||||
setFormData((prevData) => ({
|
||||
...prevData,
|
||||
[name]: value,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
updateStyle(customStyle.styleSlug, formData);
|
||||
};
|
||||
|
||||
const handleDelete = async () => {
|
||||
const isConfirmed = await confirm({
|
||||
confirmText: i18n("delete"),
|
||||
cancelText: i18n("cancel"),
|
||||
});
|
||||
|
||||
if (isConfirmed) {
|
||||
deleteStyle(customStyle.styleSlug);
|
||||
}
|
||||
};
|
||||
|
||||
const { styleName = "", styleCode = "" } = formData;
|
||||
|
||||
const textClass = useMemo(
|
||||
() => css`
|
||||
${styleCode}
|
||||
`,
|
||||
[styleCode]
|
||||
);
|
||||
|
||||
const quote = useMemo(() => {
|
||||
const q = getRandomQuote();
|
||||
if (uiLang === "en") {
|
||||
return [q.zh, q.en];
|
||||
}
|
||||
return [q.en, q.zh];
|
||||
}, [uiLang]);
|
||||
|
||||
return (
|
||||
<Stack spacing={3}>
|
||||
<Box>
|
||||
{quote[0]}
|
||||
<br />
|
||||
<span className={textClass}>{quote[1]}</span>
|
||||
</Box>
|
||||
|
||||
<TextField
|
||||
size="small"
|
||||
label={i18n("style_name")}
|
||||
name="styleName"
|
||||
value={styleName}
|
||||
onChange={handleChange}
|
||||
disabled={isBuiltin}
|
||||
/>
|
||||
<TextField
|
||||
size="small"
|
||||
label={i18n("style_code")}
|
||||
name="styleCode"
|
||||
value={styleCode}
|
||||
onChange={handleChange}
|
||||
multiline
|
||||
maxRows={10}
|
||||
disabled={isBuiltin}
|
||||
/>
|
||||
|
||||
{!isBuiltin && (
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
spacing={2}
|
||||
useFlexGap
|
||||
flexWrap="wrap"
|
||||
>
|
||||
<Button
|
||||
size="small"
|
||||
variant="contained"
|
||||
onClick={handleSave}
|
||||
disabled={!isModified}
|
||||
>
|
||||
{i18n("save")}
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
variant="outlined"
|
||||
color="error"
|
||||
onClick={handleDelete}
|
||||
>
|
||||
{i18n("delete")}
|
||||
</Button>
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
function StyleAccordion({ customStyle, deleteStyle, updateStyle, isBuiltin }) {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
|
||||
const handleChange = (e) => {
|
||||
setExpanded((pre) => !pre);
|
||||
};
|
||||
|
||||
return (
|
||||
<Accordion expanded={expanded} onChange={handleChange}>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||
<Typography
|
||||
sx={{
|
||||
overflowWrap: "anywhere",
|
||||
}}
|
||||
>
|
||||
{`${customStyle.styleName}`}
|
||||
</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
{expanded && (
|
||||
<StyleFields
|
||||
customStyle={customStyle}
|
||||
deleteStyle={deleteStyle}
|
||||
updateStyle={updateStyle}
|
||||
isBuiltin={isBuiltin}
|
||||
/>
|
||||
)}
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
);
|
||||
}
|
||||
|
||||
export default function StylesSetting() {
|
||||
const i18n = useI18n();
|
||||
const { customStyles, addStyle, deleteStyle, updateStyle } = useStyleList();
|
||||
const { builtinStyles } = useAllTextStyles();
|
||||
|
||||
const handleClick = (e) => {
|
||||
e.preventDefault();
|
||||
addStyle();
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Stack spacing={3}>
|
||||
<Box>
|
||||
<Button
|
||||
size="small"
|
||||
id="add-style-button"
|
||||
variant="contained"
|
||||
onClick={handleClick}
|
||||
startIcon={<AddIcon />}
|
||||
>
|
||||
{i18n("add")}
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
{customStyles.map((customStyle) => (
|
||||
<StyleAccordion
|
||||
key={customStyle.styleSlug}
|
||||
customStyle={customStyle}
|
||||
deleteStyle={deleteStyle}
|
||||
updateStyle={updateStyle}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
<Box>
|
||||
{builtinStyles.map((customStyle) => (
|
||||
<StyleAccordion
|
||||
key={customStyle.styleSlug}
|
||||
customStyle={customStyle}
|
||||
deleteStyle={deleteStyle}
|
||||
updateStyle={updateStyle}
|
||||
isBuiltin={true}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -25,6 +25,7 @@ import Playgound from "./Playground";
|
||||
import MouseHoverSetting from "./MouseHover";
|
||||
import SubtitleSetting from "./Subtitle";
|
||||
import Loading from "../../hooks/Loading";
|
||||
import StylesSetting from "./StylesSetting";
|
||||
|
||||
export default function Options() {
|
||||
const [error, setError] = useState("");
|
||||
@@ -107,6 +108,7 @@ export default function Options() {
|
||||
<Route path="/" element={<Layout />}>
|
||||
<Route index element={<Setting />} />
|
||||
<Route path="rules" element={<Rules />} />
|
||||
<Route path="styles" element={<StylesSetting />} />
|
||||
<Route path="input" element={<InputSetting />} />
|
||||
<Route path="tranbox" element={<Tranbox />} />
|
||||
<Route path="mousehover" element={<MouseHoverSetting />} />
|
||||
|
||||
@@ -19,12 +19,12 @@ import {
|
||||
MSG_TRANSINPUT_TOGGLE,
|
||||
OPT_LANGS_FROM,
|
||||
OPT_LANGS_TO,
|
||||
OPT_STYLE_ALL,
|
||||
} from "../../config";
|
||||
import { saveRule } from "../../libs/rules";
|
||||
import { tryClearCaches } from "../../libs/cache";
|
||||
import { kissLog } from "../../libs/log";
|
||||
import { parseUrlPattern } from "../../libs/utils";
|
||||
import { useAllTextStyles } from "../../hooks/CustomStyles";
|
||||
|
||||
export default function PopupCont({
|
||||
rule,
|
||||
@@ -37,6 +37,7 @@ export default function PopupCont({
|
||||
}) {
|
||||
const i18n = useI18n();
|
||||
const [commands, setCommands] = useState({});
|
||||
const { allTextStyles } = useAllTextStyles();
|
||||
|
||||
const handleTransToggle = async (e) => {
|
||||
try {
|
||||
@@ -384,23 +385,13 @@ export default function PopupCont({
|
||||
}
|
||||
onChange={handleChange}
|
||||
>
|
||||
{OPT_STYLE_ALL.map((item) => (
|
||||
<MenuItem key={item} value={item}>
|
||||
{i18n(item)}
|
||||
{allTextStyles.map((item) => (
|
||||
<MenuItem key={item.styleSlug} value={item.styleSlug}>
|
||||
{item.styleName}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
|
||||
{/* {OPT_STYLE_USE_COLOR.includes(textStyle) && (
|
||||
<TextField
|
||||
size="small"
|
||||
name="bgColor"
|
||||
value={bgColor}
|
||||
label={i18n("bg_color")}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
)} */}
|
||||
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
|
||||
Reference in New Issue
Block a user