Fix shortcut recording defaults
This commit is contained in:
6 files changed
+94
-42
No files matched your search
@@ -166,27 +166,27 @@ fn char_to_code(ch: char) -> Option<Code> {
|
||||
'X' => Code::KeyX,
|
||||
'Y' => Code::KeyY,
|
||||
'Z' => Code::KeyZ,
|
||||
'0' => Code::Digit0,
|
||||
'1' => Code::Digit1,
|
||||
'2' => Code::Digit2,
|
||||
'3' => Code::Digit3,
|
||||
'4' => Code::Digit4,
|
||||
'5' => Code::Digit5,
|
||||
'6' => Code::Digit6,
|
||||
'7' => Code::Digit7,
|
||||
'8' => Code::Digit8,
|
||||
'9' => Code::Digit9,
|
||||
'0' | ')' => Code::Digit0,
|
||||
'1' | '!' => Code::Digit1,
|
||||
'2' | '@' => Code::Digit2,
|
||||
'3' | '#' => Code::Digit3,
|
||||
'4' | '$' => Code::Digit4,
|
||||
'5' | '%' => Code::Digit5,
|
||||
'6' | '^' => Code::Digit6,
|
||||
'7' | '&' => Code::Digit7,
|
||||
'8' | '*' => Code::Digit8,
|
||||
'9' | '(' => Code::Digit9,
|
||||
';' | ':' => Code::Semicolon,
|
||||
',' => Code::Comma,
|
||||
'.' => Code::Period,
|
||||
'/' => Code::Slash,
|
||||
'\\' => Code::Backslash,
|
||||
'[' => Code::BracketLeft,
|
||||
']' => Code::BracketRight,
|
||||
'\'' => Code::Quote,
|
||||
'`' => Code::Backquote,
|
||||
'-' => Code::Minus,
|
||||
'=' => Code::Equal,
|
||||
',' | '<' => Code::Comma,
|
||||
'.' | '>' => Code::Period,
|
||||
'/' | '?' => Code::Slash,
|
||||
'\\' | '|' => Code::Backslash,
|
||||
'[' | '{' => Code::BracketLeft,
|
||||
']' | '}' => Code::BracketRight,
|
||||
'\'' | '"' => Code::Quote,
|
||||
'`' | '~' => Code::Backquote,
|
||||
'-' | '_' => Code::Minus,
|
||||
'=' | '+' => Code::Equal,
|
||||
' ' => Code::Space,
|
||||
_ => return None,
|
||||
};
|
||||
@@ -228,4 +228,26 @@ mod tests {
|
||||
Some(HotkeyTrigger::RightControl)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accepts_shifted_printable_aliases() {
|
||||
let cases = [
|
||||
("?", Code::Slash),
|
||||
("!", Code::Digit1),
|
||||
(":", Code::Semicolon),
|
||||
("+", Code::Equal),
|
||||
("_", Code::Minus),
|
||||
("{", Code::BracketLeft),
|
||||
("|", Code::Backslash),
|
||||
];
|
||||
for (primary, expected) in cases {
|
||||
let binding = ShortcutBinding {
|
||||
primary: primary.into(),
|
||||
modifiers: vec!["shift".into()],
|
||||
};
|
||||
let parsed = parse_global_hotkey(&binding).expect("shifted printable parses");
|
||||
assert_eq!(parsed.key, expected);
|
||||
assert!(parsed.mods.contains(Modifiers::SHIFT));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useRef, useState, type CSSProperties, type KeyboardEvent } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { formatComboLabel } from '../lib/hotkey';
|
||||
import { currentPlatform, formatComboLabel } from '../lib/hotkey';
|
||||
import { setShortcutRecordingActive, validateShortcutBinding } from '../lib/ipc';
|
||||
import type { ShortcutBinding } from '../lib/types';
|
||||
|
||||
@@ -154,7 +154,7 @@ export function ShortcutRecorder({
|
||||
|
||||
function modifiersFromKeyboardEvent(e: KeyboardEvent): string[] {
|
||||
const modifiers: string[] = [];
|
||||
if (e.metaKey && e.key !== 'Meta') modifiers.push('cmd');
|
||||
if (e.metaKey && e.key !== 'Meta') modifiers.push(currentPlatform().isMac ? 'cmd' : 'super');
|
||||
if (e.ctrlKey && e.key !== 'Control') modifiers.push('ctrl');
|
||||
if (e.altKey && e.key !== 'Alt') modifiers.push('alt');
|
||||
if (e.shiftKey && e.key !== 'Shift') modifiers.push('shift');
|
||||
@@ -176,6 +176,8 @@ function modifierPrimaryFromCode(code: string, key: string): string {
|
||||
}
|
||||
|
||||
function primaryFromKeyboardEvent(e: KeyboardEvent): string {
|
||||
const printable = primaryFromPrintableCode(e.code);
|
||||
if (printable) return printable;
|
||||
if (e.key.length === 1) return e.key;
|
||||
const codeToName: Record<string, string> = {
|
||||
Space: 'Space',
|
||||
@@ -195,3 +197,23 @@ function primaryFromKeyboardEvent(e: KeyboardEvent): string {
|
||||
if (/^F\d{1,2}$/.test(e.key)) return e.key;
|
||||
return codeToName[e.code] || e.key;
|
||||
}
|
||||
|
||||
function primaryFromPrintableCode(code: string): string {
|
||||
if (/^Key[A-Z]$/.test(code)) return code.slice(3);
|
||||
if (/^Digit[0-9]$/.test(code)) return code.slice(5);
|
||||
const codeToPrimary: Record<string, string> = {
|
||||
Backquote: '`',
|
||||
Minus: '-',
|
||||
Equal: '=',
|
||||
BracketLeft: '[',
|
||||
BracketRight: ']',
|
||||
Backslash: '\\',
|
||||
Semicolon: ';',
|
||||
Quote: "'",
|
||||
Comma: ',',
|
||||
Period: '.',
|
||||
Slash: '/',
|
||||
IntlBackslash: '\\',
|
||||
};
|
||||
return codeToPrimary[code] || '';
|
||||
}
|
||||
@@ -1,6 +1,17 @@
|
||||
import i18n from '../i18n';
|
||||
import type { ComboBinding, HotkeyBinding, HotkeyTrigger, QaHotkeyBinding, ShortcutBinding } from './types';
|
||||
|
||||
export function defaultQaShortcut(): ShortcutBinding {
|
||||
return {
|
||||
primary: ';',
|
||||
modifiers: defaultAppShortcutModifiers(),
|
||||
};
|
||||
}
|
||||
|
||||
export function defaultAppShortcutModifiers(): string[] {
|
||||
return currentPlatform().isMac ? ['cmd', 'shift'] : ['ctrl', 'shift'];
|
||||
}
|
||||
|
||||
export function getHotkeyTriggerLabel(trigger: HotkeyTrigger | null | undefined): string {
|
||||
if (!trigger) return i18n.t('hotkey.fallback');
|
||||
if (trigger === 'custom') return i18n.t('hotkey.triggers.custom');
|
||||
@@ -72,9 +83,10 @@ export function formatComboLabel(binding: ComboBinding | QaHotkeyBinding | Short
|
||||
return parts.join(platform.isMac ? '' : '+');
|
||||
}
|
||||
|
||||
function currentPlatform(): { isMac: boolean; isWindows: boolean } {
|
||||
const platform = navigator.platform || '';
|
||||
const userAgent = navigator.userAgent || '';
|
||||
export function currentPlatform(): { isMac: boolean; isWindows: boolean } {
|
||||
const nav = typeof navigator === 'undefined' ? null : navigator;
|
||||
const platform = nav?.platform || '';
|
||||
const userAgent = nav?.userAgent || '';
|
||||
return {
|
||||
isMac: platform.includes('Mac') || userAgent.includes('Mac'),
|
||||
isWindows: platform.includes('Win') || userAgent.includes('Windows'),
|
||||
@@ -110,7 +122,7 @@ function formatPrimary(primary: string): string {
|
||||
return trimmed.toUpperCase();
|
||||
}
|
||||
// 常见命名键的 macOS 符号
|
||||
const isMac = navigator.platform.includes('Mac') || navigator.userAgent.includes('Mac');
|
||||
const isMac = currentPlatform().isMac;
|
||||
if (isMac) {
|
||||
switch (trimmed.toLowerCase()) {
|
||||
case 'space': return '\u2423';
|
||||
|
||||
@@ -19,6 +19,7 @@ import type {
|
||||
VocabPresetStore,
|
||||
} from './types';
|
||||
import { OL_DATA } from './mockData';
|
||||
import { defaultAppShortcutModifiers, defaultQaShortcut, formatComboLabel } from './hotkey';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
@@ -54,12 +55,12 @@ const mockSettings: UserPreferences = {
|
||||
allowNonTsfInsertionFallback: true,
|
||||
workingLanguages: ['简体中文'],
|
||||
translationTargetLanguage: '',
|
||||
qaHotkey: { primary: ';', modifiers: ['cmd', 'shift'] },
|
||||
qaHotkey: defaultQaShortcut(),
|
||||
qaSaveHistory: false,
|
||||
customComboHotkey: null,
|
||||
translationHotkey: { primary: 'Shift', modifiers: [] },
|
||||
switchStyleHotkey: { primary: 'S', modifiers: ['cmd', 'shift'] },
|
||||
openAppHotkey: { primary: 'O', modifiers: ['cmd', 'shift'] },
|
||||
switchStyleHotkey: { primary: 'S', modifiers: defaultAppShortcutModifiers() },
|
||||
openAppHotkey: { primary: 'O', modifiers: defaultAppShortcutModifiers() },
|
||||
};
|
||||
|
||||
const mockHotkeyCapability: HotkeyCapability = {
|
||||
@@ -293,7 +294,7 @@ export function restartApp(): Promise<void> {
|
||||
// 详见 issue #118。后端会发 `qa:state` / `qa:dismiss` 事件;前端通过下面四个
|
||||
// 命令查询与控制 QA 浮窗。
|
||||
export function getQaHotkeyLabel(): Promise<string> {
|
||||
return invokeOrMock('get_qa_hotkey_label', undefined, () => 'Cmd+Shift+;');
|
||||
return invokeOrMock('get_qa_hotkey_label', undefined, () => formatComboLabel(defaultQaShortcut()));
|
||||
}
|
||||
|
||||
export function setQaHotkey(binding: QaHotkeyBinding | null): Promise<void> {
|
||||
|
||||
@@ -9,13 +9,14 @@ import { useTranslation } from 'react-i18next';
|
||||
import { Card, PageHeader } from './_atoms';
|
||||
import { useHotkeySettings } from '../state/HotkeySettingsContext';
|
||||
import { setQaHotkey } from '../lib/ipc';
|
||||
import { formatComboLabel } from '../lib/hotkey';
|
||||
import { defaultQaShortcut, formatComboLabel } from '../lib/hotkey';
|
||||
import { ShortcutRecorder } from '../components/ShortcutRecorder';
|
||||
|
||||
export function SelectionAsk() {
|
||||
const { t } = useTranslation();
|
||||
const { prefs, updatePrefs: savePrefs } = useHotkeySettings();
|
||||
const defaultHotkeyLabel = 'Cmd+Shift+;';
|
||||
const defaultQaHotkey = defaultQaShortcut();
|
||||
const defaultHotkeyLabel = formatComboLabel(defaultQaHotkey);
|
||||
const recordHotkeyLabel = prefs ? formatComboLabel(prefs.dictationHotkey) : '快捷键';
|
||||
|
||||
if (!prefs) {
|
||||
@@ -79,8 +80,9 @@ export function SelectionAsk() {
|
||||
</div>
|
||||
<button
|
||||
onClick={async () => {
|
||||
await setQaHotkey(enabled ? null : { primary: ';', modifiers: ['cmd', 'shift'] });
|
||||
await savePrefs({ ...prefs, qaHotkey: enabled ? null : { primary: ';', modifiers: ['cmd', 'shift'] } });
|
||||
const nextHotkey = enabled ? null : defaultQaHotkey;
|
||||
await setQaHotkey(nextHotkey);
|
||||
await savePrefs({ ...prefs, qaHotkey: nextHotkey });
|
||||
}}
|
||||
style={{ fontSize: 12, padding: '5px 14px', background: enabled ? 'rgba(0,0,0,0.06)' : 'var(--ol-blue)', color: enabled ? 'var(--ol-ink-2)' : '#fff', border: 0, borderRadius: 6, fontFamily: 'inherit', fontWeight: 500, cursor: 'default', marginBottom: 10 }}
|
||||
>
|
||||
|
||||
@@ -9,6 +9,7 @@ import { ShortcutRecorder } from '../components/ShortcutRecorder';
|
||||
import { isDialogStatus, UpdateDialog, useAutoUpdate } from '../components/AutoUpdate';
|
||||
import { APP_VERSION_LABEL } from '../lib/appVersion';
|
||||
import { isHotkeyModeMigrationNoticeActive } from '../lib/hotkeyMigration';
|
||||
import { defaultQaShortcut } from '../lib/hotkey';
|
||||
import {
|
||||
checkAccessibilityPermission,
|
||||
checkMicrophonePermission,
|
||||
@@ -36,7 +37,6 @@ import type {
|
||||
HotkeyMode,
|
||||
HotkeyStatus,
|
||||
PermissionStatus,
|
||||
ShortcutBinding,
|
||||
WindowsImeStatus,
|
||||
} from '../lib/types';
|
||||
import { useHotkeySettings } from '../state/HotkeySettingsContext';
|
||||
@@ -975,13 +975,6 @@ function ShortcutsSection() {
|
||||
);
|
||||
}
|
||||
|
||||
function defaultQaShortcut(): ShortcutBinding {
|
||||
const isMac = navigator.platform.includes('Mac') || navigator.userAgent.includes('Mac');
|
||||
return isMac
|
||||
? { primary: ';', modifiers: ['cmd', 'shift'] }
|
||||
: { primary: ';', modifiers: ['ctrl', 'shift'] };
|
||||
}
|
||||
|
||||
function PermissionsSection() {
|
||||
const { t } = useTranslation();
|
||||
const [accessibility, setAccessibility] = useState<PermissionStatus | 'loading'>('loading');
|
||||
|
||||
Reference in new issue
Block a user