diff --git a/src/config/i18n.js b/src/config/i18n.js index c674564..ab4b11e 100644 --- a/src/config/i18n.js +++ b/src/config/i18n.js @@ -547,4 +547,28 @@ export const I18N = { zh: `全局规则`, 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_count: { + zh: `触发翻译连击次数`, + en: `Trigger Translation Press Nunber`, + }, }; diff --git a/src/config/index.js b/src/config/index.js index 3d4b2fa..702ef8d 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -189,6 +189,7 @@ export const DEFAULT_COLOR = "#209CEE"; // 默认高亮背景色/线条颜色 export const GLOBLA_RULE = { pattern: "*", selector: DEFAULT_SELECTOR, + inputSelector: "", translator: OPT_TRANS_MICROSOFT, fromLang: "auto", toLang: "zh-CN", @@ -198,6 +199,15 @@ export const GLOBLA_RULE = { textDiyStyle: "", }; +export const DEFAULT_INPUT_RULE = { + transOpen: true, + translator: OPT_TRANS_MICROSOFT, + fromLang: "auto", + toLang: "en", + triggerShortcut: ["Alt", "i"], + triggerCount: 1, +}; + // 订阅列表 export const DEFAULT_SUBRULES_LIST = [ { @@ -273,6 +283,7 @@ export const DEFAULT_SETTING = { mouseKey: OPT_MOUSEKEY_DISABLE, // 鼠标悬停翻译 shortcuts: DEFAULT_SHORTCUTS, // 快捷键 hideFab: false, // 是否隐藏按钮 + inputRule: DEFAULT_INPUT_RULE, // 输入框设置 }; export const DEFAULT_RULES = [GLOBLA_RULE]; diff --git a/src/config/rules.js b/src/config/rules.js index 4e65439..d15fdb8 100644 --- a/src/config/rules.js +++ b/src/config/rules.js @@ -10,6 +10,7 @@ export const SHADOW_KEY = ">>>"; export const DEFAULT_RULE = { pattern: "", selector: "", + inputSelector: "", translator: GLOBAL_KEY, fromLang: GLOBAL_KEY, toLang: GLOBAL_KEY, diff --git a/src/hooks/InputRule.js b/src/hooks/InputRule.js new file mode 100644 index 0000000..6d14450 --- /dev/null +++ b/src/hooks/InputRule.js @@ -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 }; +} diff --git a/src/libs/shortcut.js b/src/libs/shortcut.js index c0803f3..c29f98f 100644 --- a/src/libs/shortcut.js +++ b/src/libs/shortcut.js @@ -65,3 +65,41 @@ export const shortcutRegister = (targetKeys = [], fn, target = document) => { } }, 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(); + return shortcutListener((curkeys) => { + if (targetKeys.length > 0) { + const now = Date.now(); + if ( + (count === 0 || now - pre < timeout) && + isSameSet(new Set(targetKeys), new Set(curkeys)) + ) { + count++; + if (count === step) { + count = 0; + fn(); + } + } else { + count = 0; + } + pre = now; + } + }, target); +}; diff --git a/src/views/Options/InputSetting.js b/src/views/Options/InputSetting.js new file mode 100644 index 0000000..ca686a8 --- /dev/null +++ b/src/views/Options/InputSetting.js @@ -0,0 +1,134 @@ +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 { limitNumber } from "../../libs/utils"; +import { useI18n } from "../../hooks/I18n"; +import { OPT_TRANS_ALL, OPT_LANGS_FROM, OPT_LANGS_TO } 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"; + +export default function InputSetting() { + const i18n = useI18n(); + const { inputRule, updateInputRule } = useInputRule(); + + const handleChange = (e) => { + e.preventDefault(); + let { name, value } = e.target; + console.log({ name, value }); + switch (name) { + case "triggerCount": + value = limitNumber(value, 1, 3); + break; + default: + } + updateInputRule({ + [name]: value, + }); + }; + + const handleShortcutInput = useCallback( + (val) => { + updateInputRule({ triggerShortcut: val }); + }, + [updateInputRule] + ); + + const { + transOpen, + translator, + fromLang, + toLang, + triggerShortcut, + triggerCount, + } = inputRule; + + return ( + + + { + updateInputRule({ transOpen: !transOpen }); + }} + /> + } + label={i18n("input_box_translation")} + /> + + + {OPT_TRANS_ALL.map((item) => ( + + {item} + + ))} + + + + {OPT_LANGS_FROM.map(([lang, name]) => ( + + {name} + + ))} + + + + {OPT_LANGS_TO.map(([lang, name]) => ( + + {name} + + ))} + + + + + + {[1, 2, 3].map((val) => ( + + {val} + + ))} + + + + ); +} diff --git a/src/views/Options/Navigator.js b/src/views/Options/Navigator.js index 133b533..06d4125 100644 --- a/src/views/Options/Navigator.js +++ b/src/views/Options/Navigator.js @@ -12,6 +12,7 @@ import { useI18n } from "../../hooks/I18n"; import SyncIcon from "@mui/icons-material/Sync"; import ApiIcon from "@mui/icons-material/Api"; import SendTimeExtensionIcon from "@mui/icons-material/SendTimeExtension"; +import InputIcon from "@mui/icons-material/Input"; function LinkItem({ label, url, icon }) { const match = useMatch(url); @@ -38,6 +39,12 @@ export default function Navigator(props) { url: "/rules", icon: , }, + { + id: "input_setting", + label: i18n("input_setting"), + url: "/input", + icon: , + }, { id: "apis_setting", label: i18n("apis_setting"), diff --git a/src/views/Options/Rules.js b/src/views/Options/Rules.js index d53de57..02db939 100644 --- a/src/views/Options/Rules.js +++ b/src/views/Options/Rules.js @@ -64,6 +64,7 @@ function RuleFields({ rule, rules, setShow, setKeyword }) { const { pattern, selector, + inputSelector = "", translator, fromLang, toLang, @@ -178,6 +179,17 @@ function RuleFields({ rule, rules, setShow, setKeyword }) { onFocus={handleFocus} multiline /> + diff --git a/src/views/Options/Setting.js b/src/views/Options/Setting.js index cf35b04..f41978a 100644 --- a/src/views/Options/Setting.js +++ b/src/views/Options/Setting.js @@ -12,8 +12,6 @@ import { limitNumber } from "../../libs/utils"; import { useI18n } from "../../hooks/I18n"; import { useAlert } from "../../hooks/Alert"; 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 { UI_LANGS, @@ -26,57 +24,13 @@ import { OPT_SHORTCUT_POPUP, OPT_SHORTCUT_SETTING, } from "../../config"; -import { useEffect, useState, useRef } from "react"; import { useShortcut } from "../../hooks/Shortcut"; -import { shortcutListener } from "../../libs/shortcut"; +import ShortcutInput from "./ShortcutInput"; function ShortcutItem({ action, label }) { 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 ( - - { - setDisabled(true); - }} - /> - { - setDisabled(false); - }} - > - {} - - + ); } diff --git a/src/views/Options/ShortcutInput.js b/src/views/Options/ShortcutInput.js new file mode 100644 index 0000000..77cb4ae --- /dev/null +++ b/src/views/Options/ShortcutInput.js @@ -0,0 +1,55 @@ +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 }) { + 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 ( + + (item === " " ? "Space" : item)).join(" + ")} + fullWidth + inputRef={inputRef} + disabled={disabled} + onBlur={() => { + setDisabled(true); + }} + /> + { + setDisabled(false); + }} + > + {} + + + ); +} diff --git a/src/views/Options/index.js b/src/views/Options/index.js index ec8d5e6..9951293 100644 --- a/src/views/Options/index.js +++ b/src/views/Options/index.js @@ -19,6 +19,7 @@ import { adaptScript } from "../../libs/gm"; import Alert from "@mui/material/Alert"; import Apis from "./Apis"; import Webfix from "./Webfix"; +import InputSetting from "./InputSetting"; export default function Options() { const [error, setError] = useState(""); @@ -82,16 +83,20 @@ export default function Options() { - Install Userscript for Tampermonkey/Violentmonkey 1 (油猴脚本 安装地址 1) + Install Userscript for Tampermonkey/Violentmonkey 1 (油猴脚本 + 安装地址 1) - Install Userscript for Tampermonkey/Violentmonkey 2 (油猴脚本 安装地址 2) + Install Userscript for Tampermonkey/Violentmonkey 2 (油猴脚本 + 安装地址 2) - Install Userscript for iOS Safari 1 (油猴脚本 iOS Safari专用 安装地址 1) + Install Userscript for iOS Safari 1 (油猴脚本 iOS Safari专用 + 安装地址 1) - Install Userscript for iOS Safari 2 (油猴脚本 iOS Safari专用 安装地址 2) + Install Userscript for iOS Safari 2 (油猴脚本 iOS Safari专用 + 安装地址 2) Open Options Page 1 (打开设置页面 1) @@ -126,6 +131,7 @@ export default function Options() { }> } /> } /> + } /> } /> } /> } />