Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
189b7f480a | ||
|
|
5e3aa7e2d1 | ||
|
|
730be678ef | ||
|
|
9293f422f3 | ||
|
|
6e8158bb34 | ||
|
|
3078d3ca91 | ||
|
|
947e1c7f08 | ||
|
|
938c123412 | ||
|
|
e7a57ad3b2 | ||
|
|
1e40f81bf7 | ||
|
|
72b2f44e32 | ||
|
|
76f54461e7 | ||
|
|
14ca13e31d | ||
|
|
556fd71275 | ||
|
|
a8002bba9f | ||
|
|
ddd9371fbd | ||
|
|
0ea97b73e3 | ||
|
|
f8c8a4ebeb | ||
|
|
5f613ab558 | ||
|
|
56281f9e82 | ||
|
|
5e8743dbb7 | ||
|
|
f4e4c84712 |
2
.env
2
.env
@@ -2,7 +2,7 @@ GENERATE_SOURCEMAP=false
|
|||||||
|
|
||||||
REACT_APP_NAME=KISS Translator
|
REACT_APP_NAME=KISS Translator
|
||||||
REACT_APP_NAME_CN=简约翻译
|
REACT_APP_NAME_CN=简约翻译
|
||||||
REACT_APP_VERSION=1.7.0
|
REACT_APP_VERSION=1.7.1
|
||||||
|
|
||||||
REACT_APP_HOMEPAGE=https://github.com/fishjar/kiss-translator
|
REACT_APP_HOMEPAGE=https://github.com/fishjar/kiss-translator
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ If you also like a little more simplicity, welcome to pick it up.
|
|||||||
- [x] Supports multiple translation services
|
- [x] Supports multiple translation services
|
||||||
- [x] Google/Microsoft/DeepL/OpenAI
|
- [x] Google/Microsoft/DeepL/OpenAI
|
||||||
- [x] Custom translation interface
|
- [x] Custom translation interface
|
||||||
|
- [x] Support input box translation
|
||||||
|
- [x] Support YouTube subtitle translation
|
||||||
- [x] Data synchronization function
|
- [x] Data synchronization function
|
||||||
- [x] Custom rules + rule subscription
|
- [x] Custom rules + rule subscription
|
||||||
- [x] Custom style
|
- [x] Custom style
|
||||||
@@ -32,6 +34,7 @@ If you also like a little more simplicity, welcome to pick it up.
|
|||||||
- `Alt+C` Toggle Styles
|
- `Alt+C` Toggle Styles
|
||||||
- `Alt+K` Open Popup
|
- `Alt+K` Open Popup
|
||||||
- `Alt+O` Open Options
|
- `Alt+O` Open Options
|
||||||
|
- `Alt+I` Input Box Translation
|
||||||
|
|
||||||
## Download
|
## Download
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,8 @@
|
|||||||
- [x] 支持多种翻译服务
|
- [x] 支持多种翻译服务
|
||||||
- [x] Google/Microsoft/DeepL/OpenAI
|
- [x] Google/Microsoft/DeepL/OpenAI
|
||||||
- [x] 自定义翻译接口
|
- [x] 自定义翻译接口
|
||||||
|
- [x] 支持输入框翻译
|
||||||
|
- [x] 支持 YouTube 字幕翻译
|
||||||
- [x] 数据同步功能
|
- [x] 数据同步功能
|
||||||
- [x] 自定义规则 + 规则订阅
|
- [x] 自定义规则 + 规则订阅
|
||||||
- [x] 自定义样式
|
- [x] 自定义样式
|
||||||
@@ -32,6 +34,7 @@
|
|||||||
- `Alt+C` 切换样式
|
- `Alt+C` 切换样式
|
||||||
- `Alt+K` 打开弹窗
|
- `Alt+K` 打开弹窗
|
||||||
- `Alt+O` 打开设置
|
- `Alt+O` 打开设置
|
||||||
|
- `Alt+I` 输入框翻译
|
||||||
|
|
||||||
## 下载
|
## 下载
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "kiss-translator",
|
"name": "kiss-translator",
|
||||||
"description": "A minimalist bilingual translation Extension & Greasemonkey Script",
|
"description": "A minimalist bilingual translation Extension & Greasemonkey Script",
|
||||||
"version": "1.7.0",
|
"version": "1.7.1",
|
||||||
"author": "Gabe<yugang2002@gmail.com>",
|
"author": "Gabe<yugang2002@gmail.com>",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -84,6 +84,11 @@
|
|||||||
>
|
>
|
||||||
</p>
|
</p>
|
||||||
</h2>
|
</h2>
|
||||||
|
<hr />
|
||||||
|
<input id="input1" style="width: 80%;" />
|
||||||
|
<hr />
|
||||||
|
<textarea id="textarea1" style="width: 80%;">test</textarea>
|
||||||
|
<hr />
|
||||||
<div id="addtitle"></div>
|
<div id="addtitle"></div>
|
||||||
<h2>Shadow 1</h2>
|
<h2>Shadow 1</h2>
|
||||||
<div id="shadow1"></div>
|
<div id="shadow1"></div>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"name": "__MSG_app_name__",
|
"name": "__MSG_app_name__",
|
||||||
"description": "__MSG_app_description__",
|
"description": "__MSG_app_description__",
|
||||||
"version": "1.7.0",
|
"version": "1.7.1",
|
||||||
"default_locale": "en",
|
"default_locale": "en",
|
||||||
"author": "Gabe<yugang2002@gmail.com>",
|
"author": "Gabe<yugang2002@gmail.com>",
|
||||||
"homepage_url": "https://github.com/fishjar/kiss-translator",
|
"homepage_url": "https://github.com/fishjar/kiss-translator",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"name": "__MSG_app_name__",
|
"name": "__MSG_app_name__",
|
||||||
"description": "__MSG_app_description__",
|
"description": "__MSG_app_description__",
|
||||||
"version": "1.7.0",
|
"version": "1.7.1",
|
||||||
"default_locale": "en",
|
"default_locale": "en",
|
||||||
"author": "Gabe<yugang2002@gmail.com>",
|
"author": "Gabe<yugang2002@gmail.com>",
|
||||||
"homepage_url": "https://github.com/fishjar/kiss-translator",
|
"homepage_url": "https://github.com/fishjar/kiss-translator",
|
||||||
|
|||||||
@@ -547,4 +547,44 @@ export const I18N = {
|
|||||||
zh: `全局规则`,
|
zh: `全局规则`,
|
||||||
en: `Global Rule`,
|
en: `Global Rule`,
|
||||||
},
|
},
|
||||||
|
input_setting: {
|
||||||
|
zh: `输入框设置`,
|
||||||
|
en: `Input Box Setting`,
|
||||||
|
},
|
||||||
|
input_box_translation: {
|
||||||
|
zh: `启用输入框翻译`,
|
||||||
|
en: `Input Box Translation`,
|
||||||
|
},
|
||||||
|
input_selector: {
|
||||||
|
zh: `输入框选择器`,
|
||||||
|
en: `Input Selector`,
|
||||||
|
},
|
||||||
|
input_selector_helper: {
|
||||||
|
zh: `用于输入框翻译。`,
|
||||||
|
en: `Used for input box translation.`,
|
||||||
|
},
|
||||||
|
trigger_trans_shortcut: {
|
||||||
|
zh: `触发翻译快捷键`,
|
||||||
|
en: `Trigger Translation Shortcut Keys`,
|
||||||
|
},
|
||||||
|
trigger_trans_shortcut_help: {
|
||||||
|
zh: `默认为单击“Alt+i”`,
|
||||||
|
en: `Default is "Alt+i"`,
|
||||||
|
},
|
||||||
|
shortcut_press_count: {
|
||||||
|
zh: `快捷键连击次数`,
|
||||||
|
en: `Shortcut Press Number`,
|
||||||
|
},
|
||||||
|
combo_timeout: {
|
||||||
|
zh: `连击超时时间 (10-1000ms)`,
|
||||||
|
en: `Combo Timeout (10-1000ms)`,
|
||||||
|
},
|
||||||
|
input_trans_start_sign: {
|
||||||
|
zh: `翻译起始标识`,
|
||||||
|
en: `Translation Start Sign`,
|
||||||
|
},
|
||||||
|
input_trans_start_sign_help: {
|
||||||
|
zh: `标识后面可以加目标语言代码,如: “/en 你好”、“/zh hello”`,
|
||||||
|
en: `The target language code can be added after the sign, such as: "/en 你好", "/zh hello"`,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -137,6 +137,7 @@ export const OPT_LANGS_SPECIAL = {
|
|||||||
),
|
),
|
||||||
[OPT_TRANS_CUSTOMIZE]: new Map([["auto", ""]]),
|
[OPT_TRANS_CUSTOMIZE]: new Map([["auto", ""]]),
|
||||||
};
|
};
|
||||||
|
export const OPT_LANGS_LIST = OPT_LANGS_TO.map(([lang]) => lang);
|
||||||
|
|
||||||
export const OPT_STYLE_NONE = "style_none"; // 无
|
export const OPT_STYLE_NONE = "style_none"; // 无
|
||||||
export const OPT_STYLE_LINE = "under_line"; // 下划线
|
export const OPT_STYLE_LINE = "under_line"; // 下划线
|
||||||
@@ -198,6 +199,20 @@ export const GLOBLA_RULE = {
|
|||||||
textDiyStyle: "",
|
textDiyStyle: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 输入框翻译
|
||||||
|
export const OPT_INPUT_TRANS_SIGNS = ["/", "//", "\\", "\\\\", ">", ">>"];
|
||||||
|
export const DEFAULT_INPUT_SHORTCUT = ["Alt", "i"];
|
||||||
|
export const DEFAULT_INPUT_RULE = {
|
||||||
|
transOpen: true,
|
||||||
|
translator: OPT_TRANS_MICROSOFT,
|
||||||
|
fromLang: "auto",
|
||||||
|
toLang: "en",
|
||||||
|
triggerShortcut: DEFAULT_INPUT_SHORTCUT,
|
||||||
|
triggerCount: 1,
|
||||||
|
triggerTime: 200,
|
||||||
|
transSign: OPT_INPUT_TRANS_SIGNS[0],
|
||||||
|
};
|
||||||
|
|
||||||
// 订阅列表
|
// 订阅列表
|
||||||
export const DEFAULT_SUBRULES_LIST = [
|
export const DEFAULT_SUBRULES_LIST = [
|
||||||
{
|
{
|
||||||
@@ -273,6 +288,7 @@ export const DEFAULT_SETTING = {
|
|||||||
mouseKey: OPT_MOUSEKEY_DISABLE, // 鼠标悬停翻译
|
mouseKey: OPT_MOUSEKEY_DISABLE, // 鼠标悬停翻译
|
||||||
shortcuts: DEFAULT_SHORTCUTS, // 快捷键
|
shortcuts: DEFAULT_SHORTCUTS, // 快捷键
|
||||||
hideFab: false, // 是否隐藏按钮
|
hideFab: false, // 是否隐藏按钮
|
||||||
|
inputRule: DEFAULT_INPUT_RULE, // 输入框设置
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DEFAULT_RULES = [GLOBLA_RULE];
|
export const DEFAULT_RULES = [GLOBLA_RULE];
|
||||||
|
|||||||
18
src/hooks/InputRule.js
Normal file
18
src/hooks/InputRule.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { useCallback } from "react";
|
||||||
|
import { DEFAULT_INPUT_RULE } from "../config";
|
||||||
|
import { useSetting } from "./Setting";
|
||||||
|
|
||||||
|
export function useInputRule() {
|
||||||
|
const { setting, updateSetting } = useSetting();
|
||||||
|
const inputRule = setting?.inputRule || DEFAULT_INPUT_RULE;
|
||||||
|
|
||||||
|
const updateInputRule = useCallback(
|
||||||
|
async (obj) => {
|
||||||
|
Object.assign(inputRule, obj);
|
||||||
|
await updateSetting({ inputRule });
|
||||||
|
},
|
||||||
|
[inputRule, updateSetting]
|
||||||
|
);
|
||||||
|
|
||||||
|
return { inputRule, updateInputRule };
|
||||||
|
}
|
||||||
@@ -65,3 +65,48 @@ export const shortcutRegister = (targetKeys = [], fn, target = document) => {
|
|||||||
}
|
}
|
||||||
}, target);
|
}, target);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册连续快捷键
|
||||||
|
* @param {*} targetKeys
|
||||||
|
* @param {*} fn
|
||||||
|
* @param {*} step
|
||||||
|
* @param {*} timeout
|
||||||
|
* @param {*} target
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const stepShortcutRegister = (
|
||||||
|
targetKeys = [],
|
||||||
|
fn,
|
||||||
|
step = 3,
|
||||||
|
timeout = 500,
|
||||||
|
target = document
|
||||||
|
) => {
|
||||||
|
let count = 0;
|
||||||
|
let pre = Date.now();
|
||||||
|
let timer;
|
||||||
|
return shortcutListener((curkeys, allkeys) => {
|
||||||
|
timer && clearTimeout(timer);
|
||||||
|
timer = setTimeout(() => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
count = 0;
|
||||||
|
}, timeout);
|
||||||
|
|
||||||
|
if (targetKeys.length > 0 && curkeys.length === 0) {
|
||||||
|
const now = Date.now();
|
||||||
|
if (
|
||||||
|
(count === 0 || now - pre < timeout) &&
|
||||||
|
isSameSet(new Set(targetKeys), new Set(allkeys))
|
||||||
|
) {
|
||||||
|
count++;
|
||||||
|
if (count === step) {
|
||||||
|
count = 0;
|
||||||
|
fn();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
count = 0;
|
||||||
|
}
|
||||||
|
pre = now;
|
||||||
|
}
|
||||||
|
}, target);
|
||||||
|
};
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ import {
|
|||||||
updateSync,
|
updateSync,
|
||||||
setSubRules,
|
setSubRules,
|
||||||
getSubRules,
|
getSubRules,
|
||||||
updateSetting,
|
|
||||||
} from "./storage";
|
} from "./storage";
|
||||||
import { apiFetch } from "../apis";
|
import { apiFetch } from "../apis";
|
||||||
import { checkRules } from "./rules";
|
import { checkRules } from "./rules";
|
||||||
import { isAllchar } from "./utils";
|
import { isAllchar } from "./utils";
|
||||||
|
import { syncWebfix } from "./webfix";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新缓存同步时间
|
* 更新缓存同步时间
|
||||||
@@ -63,13 +63,13 @@ export const trySyncAllSubRules = async ({ subrulesList }, isBg = false) => {
|
|||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const interval = 24 * 60 * 60 * 1000; // 间隔一天
|
const interval = 24 * 60 * 60 * 1000; // 间隔一天
|
||||||
if (now - subRulesSyncAt > interval) {
|
if (now - subRulesSyncAt > interval) {
|
||||||
|
// 同步订阅规则
|
||||||
await syncAllSubRules(subrulesList, isBg);
|
await syncAllSubRules(subrulesList, isBg);
|
||||||
await updateSync({ subRulesSyncAt: now });
|
await updateSync({ subRulesSyncAt: now });
|
||||||
|
|
||||||
|
// 同步修复规则
|
||||||
|
await syncWebfix(process.env.REACT_APP_WEBFIXURL);
|
||||||
}
|
}
|
||||||
subrulesList.forEach((item) => {
|
|
||||||
item.syncAt = now;
|
|
||||||
});
|
|
||||||
await updateSetting({ subrulesList });
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("[try sync all subrules]", err);
|
console.log("[try sync all subrules]", err);
|
||||||
}
|
}
|
||||||
|
|||||||
34
src/libs/svg.js
Normal file
34
src/libs/svg.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
export const loadingSvg = `
|
||||||
|
<svg viewBox="0 0 100 100" style="display:inline-block; width:100%; height: 100%;">
|
||||||
|
<circle fill="#209CEE" stroke="none" cx="6" cy="50" r="6">
|
||||||
|
<animateTransform
|
||||||
|
attributeName="transform"
|
||||||
|
dur="1s"
|
||||||
|
type="translate"
|
||||||
|
values="0 15 ; 0 -15; 0 15"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
begin="0.1"
|
||||||
|
/>
|
||||||
|
</circle>
|
||||||
|
<circle fill="#209CEE" stroke="none" cx="30" cy="50" r="6">
|
||||||
|
<animateTransform
|
||||||
|
attributeName="transform"
|
||||||
|
dur="1s"
|
||||||
|
type="translate"
|
||||||
|
values="0 10 ; 0 -10; 0 10"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
begin="0.2"
|
||||||
|
/>
|
||||||
|
</circle>
|
||||||
|
<circle fill="#209CEE" stroke="none" cx="54" cy="50" r="6">
|
||||||
|
<animateTransform
|
||||||
|
attributeName="transform"
|
||||||
|
dur="1s"
|
||||||
|
type="translate"
|
||||||
|
values="0 5 ; 0 -5; 0 5"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
begin="0.3"
|
||||||
|
/>
|
||||||
|
</circle>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
@@ -9,16 +9,101 @@ import {
|
|||||||
SHADOW_KEY,
|
SHADOW_KEY,
|
||||||
OPT_MOUSEKEY_DISABLE,
|
OPT_MOUSEKEY_DISABLE,
|
||||||
OPT_MOUSEKEY_MOUSEOVER,
|
OPT_MOUSEKEY_MOUSEOVER,
|
||||||
|
DEFAULT_INPUT_RULE,
|
||||||
|
DEFAULT_TRANS_APIS,
|
||||||
|
DEFAULT_INPUT_SHORTCUT,
|
||||||
|
OPT_LANGS_LIST,
|
||||||
} from "../config";
|
} from "../config";
|
||||||
import Content from "../views/Content";
|
import Content from "../views/Content";
|
||||||
import { updateFetchPool, clearFetchPool } from "./fetch";
|
import { updateFetchPool, clearFetchPool } from "./fetch";
|
||||||
import { debounce, genEventName } from "./utils";
|
import {
|
||||||
|
debounce,
|
||||||
|
genEventName,
|
||||||
|
removeEndchar,
|
||||||
|
matchInputStr,
|
||||||
|
sleep,
|
||||||
|
} from "./utils";
|
||||||
|
import { stepShortcutRegister } from "./shortcut";
|
||||||
|
import { apiTranslate } from "../apis";
|
||||||
|
import { tryDetectLang } from ".";
|
||||||
|
import { loadingSvg } from "./svg";
|
||||||
|
|
||||||
|
function isInputNode(node) {
|
||||||
|
return node.nodeName === "INPUT" || node.nodeName === "TEXTAREA";
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEditAbleNode(node) {
|
||||||
|
return node.hasAttribute("contenteditable");
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectContent(node) {
|
||||||
|
node.focus();
|
||||||
|
const range = document.createRange();
|
||||||
|
range.selectNodeContents(node);
|
||||||
|
|
||||||
|
const selection = window.getSelection();
|
||||||
|
selection.removeAllRanges();
|
||||||
|
selection.addRange(range);
|
||||||
|
}
|
||||||
|
|
||||||
|
function pasteContentEvent(node, text) {
|
||||||
|
node.focus();
|
||||||
|
const data = new DataTransfer();
|
||||||
|
data.setData("text/plain", text);
|
||||||
|
|
||||||
|
const event = new ClipboardEvent("paste", { clipboardData: data });
|
||||||
|
document.dispatchEvent(event);
|
||||||
|
data.clearData();
|
||||||
|
}
|
||||||
|
|
||||||
|
function pasteContentCommand(node, text) {
|
||||||
|
node.focus();
|
||||||
|
document.execCommand("insertText", false, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
function collapseToEnd(node) {
|
||||||
|
node.focus();
|
||||||
|
const selection = window.getSelection();
|
||||||
|
selection.collapseToEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNodeText(node) {
|
||||||
|
if (isInputNode(node)) {
|
||||||
|
return node.value;
|
||||||
|
}
|
||||||
|
return node.innerText || node.textContent || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function addLoading(node, loadingId) {
|
||||||
|
const div = document.createElement("div");
|
||||||
|
div.id = loadingId;
|
||||||
|
div.innerHTML = loadingSvg;
|
||||||
|
div.style.cssText = `
|
||||||
|
width: ${node.offsetWidth}px;
|
||||||
|
height: ${node.offsetHeight}px;
|
||||||
|
line-height: ${node.offsetHeight}px;
|
||||||
|
position: absolute;
|
||||||
|
text-align: center;
|
||||||
|
left: ${node.offsetLeft}px;
|
||||||
|
top: ${node.offsetTop}px;
|
||||||
|
z-index: 2147483647;
|
||||||
|
`;
|
||||||
|
node.offsetParent?.appendChild(div);
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeLoading(loadingId) {
|
||||||
|
const div = document.getElementById(loadingId);
|
||||||
|
if (div) {
|
||||||
|
div.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 翻译类
|
* 翻译类
|
||||||
*/
|
*/
|
||||||
export class Translator {
|
export class Translator {
|
||||||
_rule = {};
|
_rule = {};
|
||||||
|
_inputRule = {};
|
||||||
_setting = {};
|
_setting = {};
|
||||||
_rootNodes = new Set();
|
_rootNodes = new Set();
|
||||||
_tranNodes = new Map();
|
_tranNodes = new Map();
|
||||||
@@ -101,6 +186,11 @@ export class Translator {
|
|||||||
if (rule.transOpen === "true") {
|
if (rule.transOpen === "true") {
|
||||||
this._register();
|
this._register();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._inputRule = setting.inputRule || DEFAULT_INPUT_RULE;
|
||||||
|
if (this._inputRule.transOpen) {
|
||||||
|
this._registerInput();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get setting() {
|
get setting() {
|
||||||
@@ -243,6 +333,116 @@ export class Translator {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_registerInput = () => {
|
||||||
|
const {
|
||||||
|
triggerShortcut: initTriggerShortcut,
|
||||||
|
translator,
|
||||||
|
fromLang,
|
||||||
|
toLang: initToLang,
|
||||||
|
triggerCount: initTriggerCount,
|
||||||
|
triggerTime,
|
||||||
|
transSign,
|
||||||
|
} = this._inputRule;
|
||||||
|
const apiSetting = (this._setting.transApis || DEFAULT_TRANS_APIS)[
|
||||||
|
translator
|
||||||
|
];
|
||||||
|
|
||||||
|
let triggerShortcut = initTriggerShortcut;
|
||||||
|
let triggerCount = initTriggerCount;
|
||||||
|
if (triggerShortcut.length === 0) {
|
||||||
|
triggerShortcut = DEFAULT_INPUT_SHORTCUT;
|
||||||
|
triggerCount = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
stepShortcutRegister(
|
||||||
|
triggerShortcut,
|
||||||
|
async () => {
|
||||||
|
const node = document.activeElement;
|
||||||
|
if (!node || !(isInputNode(node) || isEditAbleNode(node))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let initText = getNodeText(node);
|
||||||
|
if (triggerShortcut.length === 1 && triggerShortcut[0].length === 1) {
|
||||||
|
// todo: remove multiple char
|
||||||
|
initText = removeEndchar(initText, triggerShortcut[0], triggerCount);
|
||||||
|
}
|
||||||
|
if (!initText.trim()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let text = initText;
|
||||||
|
let toLang = initToLang;
|
||||||
|
if (transSign) {
|
||||||
|
const res = matchInputStr(text, transSign);
|
||||||
|
if (res) {
|
||||||
|
let lang = res[1];
|
||||||
|
if (lang === "zh" || lang === "cn") {
|
||||||
|
lang = "zh-CN";
|
||||||
|
} else if (lang === "tw" || lang === "hk") {
|
||||||
|
lang = "zh-TW";
|
||||||
|
}
|
||||||
|
if (lang && OPT_LANGS_LIST.includes(lang)) {
|
||||||
|
toLang = lang;
|
||||||
|
}
|
||||||
|
text = res[2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.log("input -->", text);
|
||||||
|
|
||||||
|
const loadingId = "kiss-" + genEventName();
|
||||||
|
try {
|
||||||
|
addLoading(node, loadingId);
|
||||||
|
|
||||||
|
const deLang = await tryDetectLang(text);
|
||||||
|
if (deLang && toLang.includes(deLang)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [trText, isSame] = await apiTranslate({
|
||||||
|
translator,
|
||||||
|
text,
|
||||||
|
fromLang,
|
||||||
|
toLang,
|
||||||
|
apiSetting,
|
||||||
|
});
|
||||||
|
if (!trText || isSame) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isInputNode(node)) {
|
||||||
|
node.value = trText;
|
||||||
|
node.dispatchEvent(
|
||||||
|
new Event("input", { bubbles: true, cancelable: true })
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectContent(node);
|
||||||
|
await sleep(200);
|
||||||
|
|
||||||
|
pasteContentEvent(node, trText);
|
||||||
|
await sleep(200);
|
||||||
|
|
||||||
|
// todo: use includes?
|
||||||
|
if (getNodeText(node).startsWith(initText)) {
|
||||||
|
pasteContentCommand(node, trText);
|
||||||
|
await sleep(100);
|
||||||
|
} else {
|
||||||
|
collapseToEnd(node);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log("[translate input]", err.message);
|
||||||
|
} finally {
|
||||||
|
removeLoading(loadingId);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
triggerCount,
|
||||||
|
triggerTime
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
_handleMouseover = (e) => {
|
_handleMouseover = (e) => {
|
||||||
const key = this._setting.mouseKey.slice(3);
|
const key = this._setting.mouseKey.slice(3);
|
||||||
if (this._setting.mouseKey === OPT_MOUSEKEY_MOUSEOVER || e[key]) {
|
if (this._setting.mouseKey === OPT_MOUSEKEY_MOUSEOVER || e[key]) {
|
||||||
|
|||||||
@@ -179,3 +179,47 @@ export const isSameSet = (a, b) => {
|
|||||||
const s = new Set([...a, ...b]);
|
const s = new Set([...a, ...b]);
|
||||||
return s.size === a.size && s.size === b.size;
|
return s.size === a.size && s.size === b.size;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 去掉字符串末尾某个字符
|
||||||
|
* @param {*} s
|
||||||
|
* @param {*} c
|
||||||
|
* @param {*} count
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const removeEndchar = (s, c, count = 1) => {
|
||||||
|
let i = s.length;
|
||||||
|
while (i > s.length - count && s[i - 1] === c) {
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
return s.slice(0, i);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 匹配字符串及语言标识
|
||||||
|
* @param {*} str
|
||||||
|
* @param {*} sign
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const matchInputStr = (str, sign) => {
|
||||||
|
let reg = /\/([\w-]+)\s+([^]+)/;
|
||||||
|
switch (sign) {
|
||||||
|
case "//":
|
||||||
|
reg = /\/\/([\w-]+)\s+([^]+)/;
|
||||||
|
break;
|
||||||
|
case "\\":
|
||||||
|
reg = /\\([\w-]+)\s+([^]+)/;
|
||||||
|
break;
|
||||||
|
case "\\\\":
|
||||||
|
reg = /\\\\([\w-]+)\s+([^]+)/;
|
||||||
|
break;
|
||||||
|
case ">":
|
||||||
|
reg = />([\w-]+)\s+([^]+)/;
|
||||||
|
break;
|
||||||
|
case ">>":
|
||||||
|
reg = />>([\w-]+)\s+([^]+)/;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
return str.match(reg);
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,65 +1,51 @@
|
|||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import { limitNumber } from "../../libs/utils";
|
import { limitNumber } from "../../libs/utils";
|
||||||
import { isMobile } from "../../libs/mobile";
|
import { isMobile } from "../../libs/mobile";
|
||||||
import { setFab } from "../../libs/storage";
|
import { setFab } from "../../libs/storage";
|
||||||
|
import { debounce } from "../../libs/utils";
|
||||||
|
import Paper from "@mui/material/Paper";
|
||||||
|
|
||||||
const getEdgePosition = (
|
const getEdgePosition = ({
|
||||||
{ x: left, y: top, edge },
|
x: left,
|
||||||
|
y: top,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
windowWidth,
|
windowWidth,
|
||||||
windowHeight,
|
windowHeight,
|
||||||
width,
|
hover,
|
||||||
height
|
}) => {
|
||||||
) => {
|
|
||||||
const right = windowWidth - left - width;
|
const right = windowWidth - left - width;
|
||||||
const bottom = windowHeight - top - height;
|
const bottom = windowHeight - top - height;
|
||||||
const min = Math.min(left, top, right, bottom);
|
const min = Math.min(left, top, right, bottom);
|
||||||
switch (min) {
|
switch (min) {
|
||||||
case right:
|
case right:
|
||||||
edge = "right";
|
left = hover ? windowWidth - width : windowWidth - width / 2;
|
||||||
left = windowWidth - width;
|
|
||||||
break;
|
break;
|
||||||
case left:
|
case left:
|
||||||
edge = "left";
|
left = hover ? 0 : -width / 2;
|
||||||
left = 0;
|
|
||||||
break;
|
break;
|
||||||
case bottom:
|
case bottom:
|
||||||
edge = "bottom";
|
top = hover ? windowHeight - height : windowHeight - height / 2;
|
||||||
top = windowHeight - height;
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
edge = "top";
|
top = hover ? 0 : -height / 2;
|
||||||
top = 0;
|
|
||||||
}
|
}
|
||||||
left = limitNumber(left, 0, windowWidth - width);
|
return { x: left, y: top };
|
||||||
top = limitNumber(top, 0, windowHeight - height);
|
|
||||||
return { x: left, y: top, edge, hide: false };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getHidePosition = (
|
function DraggableWrapper({ children, usePaper, ...props }) {
|
||||||
{ x: left, y: top, edge },
|
if (usePaper) {
|
||||||
windowWidth,
|
return (
|
||||||
windowHeight,
|
<Paper {...props} elevation={4}>
|
||||||
width,
|
{children}
|
||||||
height
|
</Paper>
|
||||||
) => {
|
);
|
||||||
switch (edge) {
|
|
||||||
case "right":
|
|
||||||
left = windowWidth - width / 2;
|
|
||||||
break;
|
|
||||||
case "left":
|
|
||||||
left = -width / 2;
|
|
||||||
break;
|
|
||||||
case "bottom":
|
|
||||||
top = windowHeight - height / 2;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
top = -height / 2;
|
|
||||||
}
|
}
|
||||||
return { x: left, y: top, edge, hide: true };
|
return <div {...props}>{children}</div>;
|
||||||
};
|
}
|
||||||
|
|
||||||
export default function Draggable({
|
export default function Draggable({
|
||||||
windowSize,
|
windowSize: { w: windowWidth, h: windowHeight },
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
left,
|
left,
|
||||||
@@ -70,66 +56,38 @@ export default function Draggable({
|
|||||||
onMove,
|
onMove,
|
||||||
handler,
|
handler,
|
||||||
children,
|
children,
|
||||||
|
usePaper,
|
||||||
}) {
|
}) {
|
||||||
const [origin, setOrigin] = useState({
|
const [hover, setHover] = useState(false);
|
||||||
x: left,
|
const [origin, setOrigin] = useState(null);
|
||||||
y: top,
|
const [position, setPosition] = useState({ x: left, y: top });
|
||||||
px: left,
|
const setFabPosition = useMemo(() => debounce(setFab, 500), []);
|
||||||
py: top,
|
|
||||||
});
|
|
||||||
const [position, setPosition] = useState({
|
|
||||||
x: left,
|
|
||||||
y: top,
|
|
||||||
edge: null,
|
|
||||||
hide: false,
|
|
||||||
});
|
|
||||||
const [edgeTimer, setEdgeTimer] = useState(null);
|
|
||||||
|
|
||||||
const goEdge = useCallback((w, h, width, height) => {
|
|
||||||
setPosition((pre) => getEdgePosition(pre, w, h, width, height));
|
|
||||||
|
|
||||||
setEdgeTimer(
|
|
||||||
setTimeout(() => {
|
|
||||||
setPosition((pre) => getHidePosition(pre, w, h, width, height));
|
|
||||||
}, 1500)
|
|
||||||
);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handlePointerDown = (e) => {
|
const handlePointerDown = (e) => {
|
||||||
!isMobile && e.target.setPointerCapture(e.pointerId);
|
!isMobile && e.target.setPointerCapture(e.pointerId);
|
||||||
onStart && onStart();
|
onStart && onStart();
|
||||||
edgeTimer && clearTimeout(edgeTimer);
|
const { x, y } = position;
|
||||||
const { clientX, clientY } = isMobile ? e.targetTouches[0] : e;
|
const { clientX, clientY } = isMobile ? e.targetTouches[0] : e;
|
||||||
setOrigin({
|
setOrigin({ x, y, clientX, clientY });
|
||||||
x: position.x,
|
|
||||||
y: position.y,
|
|
||||||
px: clientX,
|
|
||||||
py: clientY,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePointerMove = (e) => {
|
const handlePointerMove = (e) => {
|
||||||
onMove && onMove();
|
onMove && onMove();
|
||||||
const { clientX, clientY } = isMobile ? e.targetTouches[0] : e;
|
const { clientX, clientY } = isMobile ? e.targetTouches[0] : e;
|
||||||
if (origin) {
|
if (origin) {
|
||||||
const dx = clientX - origin.px;
|
const dx = clientX - origin.clientX;
|
||||||
const dy = clientY - origin.py;
|
const dy = clientY - origin.clientY;
|
||||||
let x = origin.x + dx;
|
let x = origin.x + dx;
|
||||||
let y = origin.y + dy;
|
let y = origin.y + dy;
|
||||||
const { w, h } = windowSize;
|
x = limitNumber(x, -width / 2, windowWidth - width / 2);
|
||||||
x = limitNumber(x, 0, w - width);
|
y = limitNumber(y, 0, windowHeight - height / 2);
|
||||||
y = limitNumber(y, 0, h - height);
|
setPosition({ x, y });
|
||||||
setPosition({ x, y, edge: null, hide: false });
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePointerUp = (e) => {
|
const handlePointerUp = (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setOrigin(null);
|
setOrigin(null);
|
||||||
if (!snapEdge) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
goEdge(windowSize.w, windowSize.h, width, height);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClick = (e) => {
|
const handleClick = (e) => {
|
||||||
@@ -138,35 +96,48 @@ export default function Draggable({
|
|||||||
|
|
||||||
const handleMouseEnter = (e) => {
|
const handleMouseEnter = (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (snapEdge && position.hide) {
|
setHover(true);
|
||||||
edgeTimer && clearTimeout(edgeTimer);
|
};
|
||||||
goEdge(windowSize.w, windowSize.h, width, height);
|
|
||||||
}
|
const handleMouseLeave = (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setHover(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setOrigin(null);
|
if (!snapEdge || !!origin) {
|
||||||
if (!snapEdge) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
goEdge(windowSize.w, windowSize.h, width, height);
|
|
||||||
}, [snapEdge, goEdge, windowSize.w, windowSize.h, width, height]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
setPosition((pre) => {
|
||||||
if (position.hide) {
|
const edgePosition = getEdgePosition({
|
||||||
setFab({
|
...pre,
|
||||||
x: position.x,
|
width,
|
||||||
y: position.y,
|
height,
|
||||||
|
windowWidth,
|
||||||
|
windowHeight,
|
||||||
|
hover,
|
||||||
});
|
});
|
||||||
}
|
setFabPosition(edgePosition);
|
||||||
}, [position.x, position.y, position.hide]);
|
return edgePosition;
|
||||||
|
});
|
||||||
|
}, [
|
||||||
|
origin,
|
||||||
|
hover,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
windowWidth,
|
||||||
|
windowHeight,
|
||||||
|
snapEdge,
|
||||||
|
setFabPosition,
|
||||||
|
]);
|
||||||
|
|
||||||
const opacity = useMemo(() => {
|
const opacity = useMemo(() => {
|
||||||
if (snapEdge) {
|
if (snapEdge) {
|
||||||
return position.hide ? 0.2 : 1;
|
return hover || origin ? 1 : 0.2;
|
||||||
}
|
}
|
||||||
return origin ? 0.8 : 1;
|
return origin ? 0.8 : 1;
|
||||||
}, [origin, snapEdge, position.hide]);
|
}, [origin, snapEdge, hover]);
|
||||||
|
|
||||||
const touchProps = isMobile
|
const touchProps = isMobile
|
||||||
? {
|
? {
|
||||||
@@ -181,7 +152,8 @@ export default function Draggable({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<DraggableWrapper
|
||||||
|
usePaper={usePaper}
|
||||||
style={{
|
style={{
|
||||||
opacity,
|
opacity,
|
||||||
position: "fixed",
|
position: "fixed",
|
||||||
@@ -191,6 +163,7 @@ export default function Draggable({
|
|||||||
display: show ? "block" : "none",
|
display: show ? "block" : "none",
|
||||||
}}
|
}}
|
||||||
onMouseEnter={handleMouseEnter}
|
onMouseEnter={handleMouseEnter}
|
||||||
|
onMouseLeave={handleMouseLeave}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -202,6 +175,6 @@ export default function Draggable({
|
|||||||
{handler}
|
{handler}
|
||||||
</div>
|
</div>
|
||||||
<div>{children}</div>
|
<div>{children}</div>
|
||||||
</div>
|
</DraggableWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import Paper from "@mui/material/Paper";
|
|
||||||
import Fab from "@mui/material/Fab";
|
import Fab from "@mui/material/Fab";
|
||||||
import TranslateIcon from "@mui/icons-material/Translate";
|
import TranslateIcon from "@mui/icons-material/Translate";
|
||||||
import ThemeProvider from "../../hooks/Theme";
|
import ThemeProvider from "../../hooks/Theme";
|
||||||
@@ -9,6 +8,8 @@ import Popup from "../Popup";
|
|||||||
import { debounce } from "../../libs/utils";
|
import { debounce } from "../../libs/utils";
|
||||||
import { isGm } from "../../libs/client";
|
import { isGm } from "../../libs/client";
|
||||||
import Header from "../Popup/Header";
|
import Header from "../Popup/Header";
|
||||||
|
import Box from "@mui/material/Box";
|
||||||
|
import Divider from "@mui/material/Divider";
|
||||||
import {
|
import {
|
||||||
DEFAULT_SHORTCUTS,
|
DEFAULT_SHORTCUTS,
|
||||||
OPT_SHORTCUT_TRANSLATE,
|
OPT_SHORTCUT_TRANSLATE,
|
||||||
@@ -78,58 +79,54 @@ export default function Action({ translator, fab }) {
|
|||||||
}, [translator]);
|
}, [translator]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 注册菜单
|
if (!isGm) {
|
||||||
const menuCommandIds = [];
|
return;
|
||||||
if (isGm) {
|
|
||||||
try {
|
|
||||||
menuCommandIds.push(
|
|
||||||
GM.registerMenuCommand(
|
|
||||||
"Toggle Translate (Alt+q)",
|
|
||||||
(event) => {
|
|
||||||
translator.toggle();
|
|
||||||
setShowPopup(false);
|
|
||||||
},
|
|
||||||
"Q"
|
|
||||||
),
|
|
||||||
GM.registerMenuCommand(
|
|
||||||
"Toggle Style (Alt+c)",
|
|
||||||
(event) => {
|
|
||||||
translator.toggleStyle();
|
|
||||||
setShowPopup(false);
|
|
||||||
},
|
|
||||||
"C"
|
|
||||||
),
|
|
||||||
GM.registerMenuCommand(
|
|
||||||
"Open Menu (Alt+k)",
|
|
||||||
(event) => {
|
|
||||||
setShowPopup((pre) => !pre);
|
|
||||||
},
|
|
||||||
"K"
|
|
||||||
),
|
|
||||||
GM.registerMenuCommand(
|
|
||||||
"Open Setting (Alt+o)",
|
|
||||||
(event) => {
|
|
||||||
window.open(process.env.REACT_APP_OPTIONSPAGE, "_blank");
|
|
||||||
},
|
|
||||||
"O"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
console.log("[registerMenuCommand]", err);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
// 注册菜单
|
||||||
if (isGm) {
|
try {
|
||||||
try {
|
const menuCommandIds = [];
|
||||||
menuCommandIds.forEach((id) => {
|
menuCommandIds.push(
|
||||||
GM.unregisterMenuCommand(id);
|
GM.registerMenuCommand(
|
||||||
});
|
"Toggle Translate (Alt+q)",
|
||||||
} catch (err) {
|
(event) => {
|
||||||
//
|
translator.toggle();
|
||||||
}
|
setShowPopup(false);
|
||||||
}
|
},
|
||||||
};
|
"Q"
|
||||||
|
),
|
||||||
|
GM.registerMenuCommand(
|
||||||
|
"Toggle Style (Alt+c)",
|
||||||
|
(event) => {
|
||||||
|
translator.toggleStyle();
|
||||||
|
setShowPopup(false);
|
||||||
|
},
|
||||||
|
"C"
|
||||||
|
),
|
||||||
|
GM.registerMenuCommand(
|
||||||
|
"Open Menu (Alt+k)",
|
||||||
|
(event) => {
|
||||||
|
setShowPopup((pre) => !pre);
|
||||||
|
},
|
||||||
|
"K"
|
||||||
|
),
|
||||||
|
GM.registerMenuCommand(
|
||||||
|
"Open Setting (Alt+o)",
|
||||||
|
(event) => {
|
||||||
|
window.open(process.env.REACT_APP_OPTIONSPAGE, "_blank");
|
||||||
|
},
|
||||||
|
"O"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
menuCommandIds.forEach((id) => {
|
||||||
|
GM.unregisterMenuCommand(id);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
console.log("[registerMenuCommand]", err);
|
||||||
|
}
|
||||||
}, [translator]);
|
}, [translator]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -165,7 +162,7 @@ export default function Action({ translator, fab }) {
|
|||||||
windowSize,
|
windowSize,
|
||||||
width: fabWidth,
|
width: fabWidth,
|
||||||
height: fabWidth,
|
height: fabWidth,
|
||||||
left: fab.x ?? 0,
|
left: fab.x ?? -fabWidth,
|
||||||
top: fab.y ?? windowSize.h / 2,
|
top: fab.y ?? windowSize.h / 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -178,17 +175,17 @@ export default function Action({ translator, fab }) {
|
|||||||
show={showPopup}
|
show={showPopup}
|
||||||
onStart={handleStart}
|
onStart={handleStart}
|
||||||
onMove={handleMove}
|
onMove={handleMove}
|
||||||
|
usePaper
|
||||||
handler={
|
handler={
|
||||||
<Paper style={{ cursor: "move" }} elevation={3}>
|
<Box style={{ cursor: "move" }}>
|
||||||
<Header setShowPopup={setShowPopup} />
|
<Header setShowPopup={setShowPopup} />
|
||||||
</Paper>
|
<Divider />
|
||||||
|
</Box>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Paper>
|
{showPopup && (
|
||||||
{showPopup && (
|
<Popup setShowPopup={setShowPopup} translator={translator} />
|
||||||
<Popup setShowPopup={setShowPopup} translator={translator} />
|
)}
|
||||||
)}
|
|
||||||
</Paper>
|
|
||||||
</Draggable>
|
</Draggable>
|
||||||
<Draggable
|
<Draggable
|
||||||
key="fab"
|
key="fab"
|
||||||
|
|||||||
@@ -1,44 +1,14 @@
|
|||||||
import { DEFAULT_COLOR } from "../../config";
|
import { loadingSvg } from "../../libs/svg";
|
||||||
|
|
||||||
export default function LoadingIcon() {
|
export default function LoadingIcon() {
|
||||||
return (
|
return (
|
||||||
<svg
|
<div
|
||||||
viewBox="0 0 100 100"
|
|
||||||
style={{
|
style={{
|
||||||
maxWidth: "1.2em",
|
display: "inline-block",
|
||||||
maxHeight: "1.2em",
|
width: "1.2em",
|
||||||
|
height: "1em",
|
||||||
}}
|
}}
|
||||||
>
|
dangerouslySetInnerHTML={{ __html: loadingSvg }}
|
||||||
<circle fill={DEFAULT_COLOR} stroke="none" cx="6" cy="50" r="6">
|
/>
|
||||||
<animateTransform
|
|
||||||
attributeName="transform"
|
|
||||||
dur="1s"
|
|
||||||
type="translate"
|
|
||||||
values="0 15 ; 0 -15; 0 15"
|
|
||||||
repeatCount="indefinite"
|
|
||||||
begin="0.1"
|
|
||||||
/>
|
|
||||||
</circle>
|
|
||||||
<circle fill={DEFAULT_COLOR} stroke="none" cx="30" cy="50" r="6">
|
|
||||||
<animateTransform
|
|
||||||
attributeName="transform"
|
|
||||||
dur="1s"
|
|
||||||
type="translate"
|
|
||||||
values="0 10 ; 0 -10; 0 10"
|
|
||||||
repeatCount="indefinite"
|
|
||||||
begin="0.2"
|
|
||||||
/>
|
|
||||||
</circle>
|
|
||||||
<circle fill={DEFAULT_COLOR} stroke="none" cx="54" cy="50" r="6">
|
|
||||||
<animateTransform
|
|
||||||
attributeName="transform"
|
|
||||||
dur="1s"
|
|
||||||
type="translate"
|
|
||||||
values="0 5 ; 0 -5; 0 5"
|
|
||||||
repeatCount="indefinite"
|
|
||||||
begin="0.3"
|
|
||||||
/>
|
|
||||||
</circle>
|
|
||||||
</svg>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
178
src/views/Options/InputSetting.js
Normal file
178
src/views/Options/InputSetting.js
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
import Box from "@mui/material/Box";
|
||||||
|
import Stack from "@mui/material/Stack";
|
||||||
|
import TextField from "@mui/material/TextField";
|
||||||
|
import MenuItem from "@mui/material/MenuItem";
|
||||||
|
import { useI18n } from "../../hooks/I18n";
|
||||||
|
import {
|
||||||
|
OPT_TRANS_ALL,
|
||||||
|
OPT_LANGS_FROM,
|
||||||
|
OPT_LANGS_TO,
|
||||||
|
OPT_INPUT_TRANS_SIGNS,
|
||||||
|
} from "../../config";
|
||||||
|
import ShortcutInput from "./ShortcutInput";
|
||||||
|
import FormControlLabel from "@mui/material/FormControlLabel";
|
||||||
|
import Switch from "@mui/material/Switch";
|
||||||
|
import { useInputRule } from "../../hooks/InputRule";
|
||||||
|
import { useCallback } from "react";
|
||||||
|
import Grid from "@mui/material/Grid";
|
||||||
|
import { limitNumber } from "../../libs/utils";
|
||||||
|
|
||||||
|
export default function InputSetting() {
|
||||||
|
const i18n = useI18n();
|
||||||
|
const { inputRule, updateInputRule } = useInputRule();
|
||||||
|
|
||||||
|
const handleChange = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
let { name, value } = e.target;
|
||||||
|
switch (name) {
|
||||||
|
case "triggerTime":
|
||||||
|
value = limitNumber(value, 10, 1000);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
updateInputRule({
|
||||||
|
[name]: value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleShortcutInput = useCallback(
|
||||||
|
(val) => {
|
||||||
|
updateInputRule({ triggerShortcut: val });
|
||||||
|
},
|
||||||
|
[updateInputRule]
|
||||||
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
transOpen,
|
||||||
|
translator,
|
||||||
|
fromLang,
|
||||||
|
toLang,
|
||||||
|
triggerShortcut,
|
||||||
|
triggerCount,
|
||||||
|
triggerTime,
|
||||||
|
transSign,
|
||||||
|
} = inputRule;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Stack spacing={3}>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Switch
|
||||||
|
size="small"
|
||||||
|
name="transOpen"
|
||||||
|
checked={transOpen}
|
||||||
|
onChange={() => {
|
||||||
|
updateInputRule({ transOpen: !transOpen });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={i18n("input_box_translation")}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
select
|
||||||
|
size="small"
|
||||||
|
name="translator"
|
||||||
|
value={translator}
|
||||||
|
label={i18n("translate_service")}
|
||||||
|
onChange={handleChange}
|
||||||
|
>
|
||||||
|
{OPT_TRANS_ALL.map((item) => (
|
||||||
|
<MenuItem key={item} value={item}>
|
||||||
|
{item}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</TextField>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
select
|
||||||
|
size="small"
|
||||||
|
name="fromLang"
|
||||||
|
value={fromLang}
|
||||||
|
label={i18n("from_lang")}
|
||||||
|
onChange={handleChange}
|
||||||
|
>
|
||||||
|
{OPT_LANGS_FROM.map(([lang, name]) => (
|
||||||
|
<MenuItem key={lang} value={lang}>
|
||||||
|
{name}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</TextField>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
select
|
||||||
|
size="small"
|
||||||
|
name="toLang"
|
||||||
|
value={toLang}
|
||||||
|
label={i18n("to_lang")}
|
||||||
|
onChange={handleChange}
|
||||||
|
>
|
||||||
|
{OPT_LANGS_TO.map(([lang, name]) => (
|
||||||
|
<MenuItem key={lang} value={lang}>
|
||||||
|
{name}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</TextField>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
select
|
||||||
|
size="small"
|
||||||
|
name="transSign"
|
||||||
|
value={transSign}
|
||||||
|
label={i18n("input_trans_start_sign")}
|
||||||
|
onChange={handleChange}
|
||||||
|
helperText={i18n("input_trans_start_sign_help")}
|
||||||
|
>
|
||||||
|
<MenuItem value={""}>{i18n("style_none")}</MenuItem>
|
||||||
|
{OPT_INPUT_TRANS_SIGNS.map((item) => (
|
||||||
|
<MenuItem key={item} value={item}>
|
||||||
|
{item}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</TextField>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Grid container spacing={2} columns={12}>
|
||||||
|
<Grid item xs={12} sm={12} md={4} lg={4}>
|
||||||
|
<ShortcutInput
|
||||||
|
value={triggerShortcut}
|
||||||
|
onChange={handleShortcutInput}
|
||||||
|
label={i18n("trigger_trans_shortcut")}
|
||||||
|
helperText={i18n("trigger_trans_shortcut_help")}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} sm={12} md={4} lg={4}>
|
||||||
|
<TextField
|
||||||
|
select
|
||||||
|
fullWidth
|
||||||
|
size="small"
|
||||||
|
name="triggerCount"
|
||||||
|
value={triggerCount}
|
||||||
|
label={i18n("shortcut_press_count")}
|
||||||
|
onChange={handleChange}
|
||||||
|
>
|
||||||
|
{[1, 2, 3, 4, 5].map((val) => (
|
||||||
|
<MenuItem key={val} value={val}>
|
||||||
|
{val}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</TextField>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} sm={12} md={4} lg={4}>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
size="small"
|
||||||
|
label={i18n("combo_timeout")}
|
||||||
|
type="number"
|
||||||
|
name="triggerTime"
|
||||||
|
defaultValue={triggerTime}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ import { useI18n } from "../../hooks/I18n";
|
|||||||
import SyncIcon from "@mui/icons-material/Sync";
|
import SyncIcon from "@mui/icons-material/Sync";
|
||||||
import ApiIcon from "@mui/icons-material/Api";
|
import ApiIcon from "@mui/icons-material/Api";
|
||||||
import SendTimeExtensionIcon from "@mui/icons-material/SendTimeExtension";
|
import SendTimeExtensionIcon from "@mui/icons-material/SendTimeExtension";
|
||||||
|
import InputIcon from "@mui/icons-material/Input";
|
||||||
|
|
||||||
function LinkItem({ label, url, icon }) {
|
function LinkItem({ label, url, icon }) {
|
||||||
const match = useMatch(url);
|
const match = useMatch(url);
|
||||||
@@ -38,6 +39,12 @@ export default function Navigator(props) {
|
|||||||
url: "/rules",
|
url: "/rules",
|
||||||
icon: <DesignServicesIcon />,
|
icon: <DesignServicesIcon />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "input_setting",
|
||||||
|
label: i18n("input_setting"),
|
||||||
|
url: "/input",
|
||||||
|
icon: <InputIcon />,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "apis_setting",
|
id: "apis_setting",
|
||||||
label: i18n("apis_setting"),
|
label: i18n("apis_setting"),
|
||||||
|
|||||||
@@ -12,8 +12,6 @@ import { limitNumber } from "../../libs/utils";
|
|||||||
import { useI18n } from "../../hooks/I18n";
|
import { useI18n } from "../../hooks/I18n";
|
||||||
import { useAlert } from "../../hooks/Alert";
|
import { useAlert } from "../../hooks/Alert";
|
||||||
import { isExt } from "../../libs/client";
|
import { isExt } from "../../libs/client";
|
||||||
import IconButton from "@mui/material/IconButton";
|
|
||||||
import EditIcon from "@mui/icons-material/Edit";
|
|
||||||
import Grid from "@mui/material/Grid";
|
import Grid from "@mui/material/Grid";
|
||||||
import {
|
import {
|
||||||
UI_LANGS,
|
UI_LANGS,
|
||||||
@@ -26,57 +24,13 @@ import {
|
|||||||
OPT_SHORTCUT_POPUP,
|
OPT_SHORTCUT_POPUP,
|
||||||
OPT_SHORTCUT_SETTING,
|
OPT_SHORTCUT_SETTING,
|
||||||
} from "../../config";
|
} from "../../config";
|
||||||
import { useEffect, useState, useRef } from "react";
|
|
||||||
import { useShortcut } from "../../hooks/Shortcut";
|
import { useShortcut } from "../../hooks/Shortcut";
|
||||||
import { shortcutListener } from "../../libs/shortcut";
|
import ShortcutInput from "./ShortcutInput";
|
||||||
|
|
||||||
function ShortcutItem({ action, label }) {
|
function ShortcutItem({ action, label }) {
|
||||||
const { shortcut, setShortcut } = useShortcut(action);
|
const { shortcut, setShortcut } = useShortcut(action);
|
||||||
const [disabled, setDisabled] = useState(true);
|
|
||||||
const inputRef = useRef(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (disabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
inputRef.current.focus();
|
|
||||||
setShortcut([]);
|
|
||||||
|
|
||||||
const clearShortcut = shortcutListener((curkeys, allkeys) => {
|
|
||||||
setShortcut(allkeys);
|
|
||||||
if (curkeys.length === 0) {
|
|
||||||
setDisabled(true);
|
|
||||||
}
|
|
||||||
}, inputRef.current);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
clearShortcut();
|
|
||||||
};
|
|
||||||
}, [disabled, setShortcut]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack direction="row">
|
<ShortcutInput value={shortcut} onChange={setShortcut} label={label} />
|
||||||
<TextField
|
|
||||||
size="small"
|
|
||||||
label={label}
|
|
||||||
name={label}
|
|
||||||
value={shortcut.join(" + ")}
|
|
||||||
fullWidth
|
|
||||||
inputRef={inputRef}
|
|
||||||
disabled={disabled}
|
|
||||||
onBlur={() => {
|
|
||||||
setDisabled(true);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<IconButton
|
|
||||||
onClick={() => {
|
|
||||||
setDisabled(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{<EditIcon />}
|
|
||||||
</IconButton>
|
|
||||||
</Stack>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,7 +110,7 @@ export default function Settings() {
|
|||||||
label={i18n("fetch_limit")}
|
label={i18n("fetch_limit")}
|
||||||
type="number"
|
type="number"
|
||||||
name="fetchLimit"
|
name="fetchLimit"
|
||||||
value={fetchLimit}
|
defaultValue={fetchLimit}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -165,7 +119,7 @@ export default function Settings() {
|
|||||||
label={i18n("fetch_interval")}
|
label={i18n("fetch_interval")}
|
||||||
type="number"
|
type="number"
|
||||||
name="fetchInterval"
|
name="fetchInterval"
|
||||||
value={fetchInterval}
|
defaultValue={fetchInterval}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -174,7 +128,7 @@ export default function Settings() {
|
|||||||
label={i18n("min_translate_length")}
|
label={i18n("min_translate_length")}
|
||||||
type="number"
|
type="number"
|
||||||
name="minLength"
|
name="minLength"
|
||||||
value={minLength}
|
defaultValue={minLength}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -183,7 +137,7 @@ export default function Settings() {
|
|||||||
label={i18n("max_translate_length")}
|
label={i18n("max_translate_length")}
|
||||||
type="number"
|
type="number"
|
||||||
name="maxLength"
|
name="maxLength"
|
||||||
value={maxLength}
|
defaultValue={maxLength}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -192,7 +146,7 @@ export default function Settings() {
|
|||||||
label={i18n("num_of_newline_characters")}
|
label={i18n("num_of_newline_characters")}
|
||||||
type="number"
|
type="number"
|
||||||
name="newlineLength"
|
name="newlineLength"
|
||||||
value={newlineLength}
|
defaultValue={newlineLength}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -244,32 +198,35 @@ export default function Settings() {
|
|||||||
<MenuItem value={true}>{i18n("hide")}</MenuItem>
|
<MenuItem value={true}>{i18n("hide")}</MenuItem>
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<Grid container rowSpacing={2} columns={12}>
|
|
||||||
<Grid item xs={12} sm={12} md={3} lg={3}>
|
<Box>
|
||||||
<ShortcutItem
|
<Grid container spacing={2} columns={12}>
|
||||||
action={OPT_SHORTCUT_TRANSLATE}
|
<Grid item xs={12} sm={12} md={3} lg={3}>
|
||||||
label={i18n("toggle_translate_shortcut")}
|
<ShortcutItem
|
||||||
/>
|
action={OPT_SHORTCUT_TRANSLATE}
|
||||||
|
label={i18n("toggle_translate_shortcut")}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} sm={12} md={3} lg={3}>
|
||||||
|
<ShortcutItem
|
||||||
|
action={OPT_SHORTCUT_STYLE}
|
||||||
|
label={i18n("toggle_style_shortcut")}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} sm={12} md={3} lg={3}>
|
||||||
|
<ShortcutItem
|
||||||
|
action={OPT_SHORTCUT_POPUP}
|
||||||
|
label={i18n("toggle_popup_shortcut")}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} sm={12} md={3} lg={3}>
|
||||||
|
<ShortcutItem
|
||||||
|
action={OPT_SHORTCUT_SETTING}
|
||||||
|
label={i18n("open_setting_shortcut")}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12} sm={12} md={3} lg={3}>
|
</Box>
|
||||||
<ShortcutItem
|
|
||||||
action={OPT_SHORTCUT_STYLE}
|
|
||||||
label={i18n("toggle_style_shortcut")}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
<Grid item xs={12} sm={12} md={3} lg={3}>
|
|
||||||
<ShortcutItem
|
|
||||||
action={OPT_SHORTCUT_POPUP}
|
|
||||||
label={i18n("toggle_popup_shortcut")}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
<Grid item xs={12} sm={12} md={3} lg={3}>
|
|
||||||
<ShortcutItem
|
|
||||||
action={OPT_SHORTCUT_SETTING}
|
|
||||||
label={i18n("open_setting_shortcut")}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
56
src/views/Options/ShortcutInput.js
Normal file
56
src/views/Options/ShortcutInput.js
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import Stack from "@mui/material/Stack";
|
||||||
|
import TextField from "@mui/material/TextField";
|
||||||
|
import IconButton from "@mui/material/IconButton";
|
||||||
|
import EditIcon from "@mui/icons-material/Edit";
|
||||||
|
import { useEffect, useState, useRef } from "react";
|
||||||
|
import { shortcutListener } from "../../libs/shortcut";
|
||||||
|
|
||||||
|
export default function ShortcutInput({ value, onChange, label, helperText }) {
|
||||||
|
const [disabled, setDisabled] = useState(true);
|
||||||
|
const inputRef = useRef(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (disabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
inputRef.current.focus();
|
||||||
|
onChange([]);
|
||||||
|
|
||||||
|
const clearShortcut = shortcutListener((curkeys, allkeys) => {
|
||||||
|
onChange(allkeys);
|
||||||
|
if (curkeys.length === 0) {
|
||||||
|
setDisabled(true);
|
||||||
|
}
|
||||||
|
}, inputRef.current);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearShortcut();
|
||||||
|
};
|
||||||
|
}, [disabled, onChange]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack direction="row" alignItems="flex-start">
|
||||||
|
<TextField
|
||||||
|
size="small"
|
||||||
|
label={label}
|
||||||
|
name={label}
|
||||||
|
value={value.map((item) => (item === " " ? "Space" : item)).join(" + ")}
|
||||||
|
fullWidth
|
||||||
|
inputRef={inputRef}
|
||||||
|
disabled={disabled}
|
||||||
|
onBlur={() => {
|
||||||
|
setDisabled(true);
|
||||||
|
}}
|
||||||
|
helperText={helperText}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
setDisabled(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{<EditIcon />}
|
||||||
|
</IconButton>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -44,7 +44,7 @@ export default function SyncSetting() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const { syncUrl, syncKey } = sync;
|
const { syncUrl = "", syncKey = "" } = sync;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { adaptScript } from "../../libs/gm";
|
|||||||
import Alert from "@mui/material/Alert";
|
import Alert from "@mui/material/Alert";
|
||||||
import Apis from "./Apis";
|
import Apis from "./Apis";
|
||||||
import Webfix from "./Webfix";
|
import Webfix from "./Webfix";
|
||||||
|
import InputSetting from "./InputSetting";
|
||||||
|
|
||||||
export default function Options() {
|
export default function Options() {
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
@@ -82,16 +83,20 @@ export default function Options() {
|
|||||||
</h2>
|
</h2>
|
||||||
<Stack spacing={2}>
|
<Stack spacing={2}>
|
||||||
<Link href={process.env.REACT_APP_USERSCRIPT_DOWNLOADURL}>
|
<Link href={process.env.REACT_APP_USERSCRIPT_DOWNLOADURL}>
|
||||||
Install Userscript for Tampermonkey/Violentmonkey 1 (油猴脚本 安装地址 1)
|
Install Userscript for Tampermonkey/Violentmonkey 1 (油猴脚本
|
||||||
|
安装地址 1)
|
||||||
</Link>
|
</Link>
|
||||||
<Link href={process.env.REACT_APP_USERSCRIPT_DOWNLOADURL2}>
|
<Link href={process.env.REACT_APP_USERSCRIPT_DOWNLOADURL2}>
|
||||||
Install Userscript for Tampermonkey/Violentmonkey 2 (油猴脚本 安装地址 2)
|
Install Userscript for Tampermonkey/Violentmonkey 2 (油猴脚本
|
||||||
|
安装地址 2)
|
||||||
</Link>
|
</Link>
|
||||||
<Link href={process.env.REACT_APP_USERSCRIPT_IOS_DOWNLOADURL}>
|
<Link href={process.env.REACT_APP_USERSCRIPT_IOS_DOWNLOADURL}>
|
||||||
Install Userscript for iOS Safari 1 (油猴脚本 iOS Safari专用 安装地址 1)
|
Install Userscript for iOS Safari 1 (油猴脚本 iOS Safari专用
|
||||||
|
安装地址 1)
|
||||||
</Link>
|
</Link>
|
||||||
<Link href={process.env.REACT_APP_USERSCRIPT_IOS_DOWNLOADURL2}>
|
<Link href={process.env.REACT_APP_USERSCRIPT_IOS_DOWNLOADURL2}>
|
||||||
Install Userscript for iOS Safari 2 (油猴脚本 iOS Safari专用 安装地址 2)
|
Install Userscript for iOS Safari 2 (油猴脚本 iOS Safari专用
|
||||||
|
安装地址 2)
|
||||||
</Link>
|
</Link>
|
||||||
<Link href={process.env.REACT_APP_OPTIONSPAGE}>
|
<Link href={process.env.REACT_APP_OPTIONSPAGE}>
|
||||||
Open Options Page 1 (打开设置页面 1)
|
Open Options Page 1 (打开设置页面 1)
|
||||||
@@ -126,6 +131,7 @@ export default function Options() {
|
|||||||
<Route path="/" element={<Layout />}>
|
<Route path="/" element={<Layout />}>
|
||||||
<Route index element={<Setting />} />
|
<Route index element={<Setting />} />
|
||||||
<Route path="rules" element={<Rules />} />
|
<Route path="rules" element={<Rules />} />
|
||||||
|
<Route path="input" element={<InputSetting />} />
|
||||||
<Route path="apis" element={<Apis />} />
|
<Route path="apis" element={<Apis />} />
|
||||||
<Route path="sync" element={<SyncSetting />} />
|
<Route path="sync" element={<SyncSetting />} />
|
||||||
<Route path="webfix" element={<Webfix />} />
|
<Route path="webfix" element={<Webfix />} />
|
||||||
|
|||||||
@@ -21,7 +21,12 @@ export default function Header({ setShowPopup }) {
|
|||||||
<IconButton onClick={handleHomepage}>
|
<IconButton onClick={handleHomepage}>
|
||||||
<HomeIcon />
|
<HomeIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Box>
|
<Box
|
||||||
|
sx={{
|
||||||
|
userSelect: "none",
|
||||||
|
WebkitUserSelect: "none",
|
||||||
|
}}
|
||||||
|
>
|
||||||
{`${process.env.REACT_APP_NAME} v${process.env.REACT_APP_VERSION}`}
|
{`${process.env.REACT_APP_NAME} v${process.env.REACT_APP_VERSION}`}
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -80,13 +80,12 @@ export default function Popup({ setShowPopup, translator: tran }) {
|
|||||||
|
|
||||||
const handleSaveRule = async () => {
|
const handleSaveRule = async () => {
|
||||||
try {
|
try {
|
||||||
let host = window.location.host;
|
let href = window.location.href;
|
||||||
if (isExt) {
|
if (isExt) {
|
||||||
const tab = await getTabInfo();
|
const tab = await getTabInfo();
|
||||||
const url = new URL(tab.url);
|
href = tab.url;
|
||||||
host = url.host;
|
|
||||||
}
|
}
|
||||||
saveRule({ ...rule, pattern: host });
|
saveRule({ ...rule, pattern: href });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("[save rule]", err);
|
console.log("[save rule]", err);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user