feat(shortcuts): add close other tabs shortcut

This commit is contained in:
vrustx authored and GitHub committed 2026-07-20 23:31:26 +08:00
1 parent e143a76e1f
commit d2975fc015
7 files changed
+127 -5

No files matched your search

@@ -3,6 +3,7 @@ import { normalizeShortcutSettings, type ShortcutActionId, type ShortcutSettings
export interface ShortcutLikeEvent {
key: string;
code?: string;
metaKey?: boolean;
ctrlKey?: boolean;
altKey?: boolean;
@@ -16,6 +17,18 @@ function normalizeKey(key: string): string {
return key.length === 1 ? key.toLowerCase() : key;
}
function matchesShortcutKey(event: ShortcutLikeEvent, key: string, platform = globalThis.navigator?.platform || ""): boolean {
if (normalizeKey(event.key) === normalizeKey(key)) return true;
// KeyboardEvent.code 回退:event.key 随布局/修饰键变形时按物理键位匹配
// (macOS Option+字母 → 变形字符如 ⌥W="∑";俄文等非拉丁布局 → "Ц")。
// 仅限 Alt 组合键场景
if (!event.altKey || !/^Key[A-Z]$/.test(event.code ?? "") || !/^[A-Z]$/i.test(key)) return false;
// 非 macOS 的 Ctrl+Alt 是 AltGr 特征:用户可能在输入字符(如波兰语
// AltGr+W → "ł"),按 code 强制匹配会让全局快捷键在打字时误触发
if (!isMacShortcutPlatform(platform) && event.ctrlKey) return false;
return event.code!.slice(3).toLowerCase() === key.toLowerCase();
}
function shortcutKeyName(key: string): string | null {
if (key === " ") return "Space";
if (key === "+") return "Plus";
@@ -64,7 +77,7 @@ export function matchesModifierOnlyShortcut(event: Omit<ShortcutLikeEvent, "key"
return false;
}
export function matchesShortcut(event: ShortcutLikeEvent, shortcut: string): boolean {
export function matchesShortcut(event: ShortcutLikeEvent, shortcut: string, platform = globalThis.navigator?.platform || ""): boolean {
if (event.isComposing || !shortcut) return false;
const parts = parseShortcutParts(shortcut);
const key = parts[parts.length - 1] ?? "";
@@ -82,7 +95,7 @@ export function matchesShortcut(event: ShortcutLikeEvent, shortcut: string): boo
if (!!event.altKey !== modifiers.has("Alt")) return false;
if (!!event.shiftKey !== modifiers.has("Shift")) return false;
return normalizeKey(event.key) === normalizeKey(key);
return matchesShortcutKey(event, key, platform);
}
function actionShortcut(actionId: ShortcutActionId, shortcuts?: Partial<ShortcutSettings>): string {
@@ -99,6 +112,10 @@ export function isCloseTabShortcut(event: ShortcutLikeEvent, shortcuts?: Partial
return matchesShortcut(event, actionShortcut("closeTab", shortcuts));
}
export function isCloseOtherTabsShortcut(event: ShortcutLikeEvent, shortcuts?: Partial<ShortcutSettings>, platform = globalThis.navigator?.platform || ""): boolean {
return matchesShortcut(event, actionShortcut("closeOtherTabs", shortcuts), platform);
}
export function isSendSelectionToAiShortcut(event: ShortcutLikeEvent, shortcuts?: Partial<ShortcutSettings>): boolean {
return matchesShortcut(event, actionShortcut("sendSelectionToAi", shortcuts));
}
@@ -1,4 +1,4 @@
import { parseShortcutStrokes, shortcutDisplayParts } from "@/lib/editor/shortcutDisplay";
import { isMacShortcutPlatform, parseShortcutStrokes, shortcutDisplayParts } from "@/lib/editor/shortcutDisplay";
export type ShortcutActionId =
| "executeSql"
@@ -25,6 +25,7 @@ export type ShortcutActionId =
| "newQuery"
| "openSettings"
| "closeTab"
| "closeOtherTabs"
| "focusSearch"
| "quickOpen"
| "switchToPreviousTab"
@@ -65,6 +66,19 @@ export interface ShortcutDefinition {
export type ShortcutSettings = Record<ShortcutActionId, string>;
// closeOtherTabs 的平台相关默认键。Windows/Linux 不用 Alt+Mod(= Ctrl+Alt,
// 与国际键盘 AltGr 字符输入冲突),也不用 Ctrl+Shift+W(浏览器保留的关窗键,
// Web 形态不可拦截,closeTab 默认 Meta+W 同理);Shift+Alt+W 无浏览器保留
// 冲突(Firefox accesskey 同为 Alt+Shift+字母,属正常应用快捷键区)。
// 已知取舍:Windows 的 Alt+Shift 布局切换只在单独按下并释放时触发,
// Alt+Shift+字母会正常送达应用,多语言用户如遇干扰可自定义改键。
// macOS 的 ⌥⌘W 无上述问题
export function closeOtherTabsDefaultShortcut(platform = globalThis.navigator?.platform || ""): string {
return isMacShortcutPlatform(platform) ? "Alt+Mod+W" : "Shift+Alt+W";
}
const CLOSE_OTHER_TABS_PLATFORM_DEFAULTS = new Set(["Alt+Mod+W", "Shift+Alt+W"]);
export const SHORTCUT_DEFINITIONS: ShortcutDefinition[] = [
{
id: "executeSql",
@@ -210,6 +224,12 @@ export const SHORTCUT_DEFINITIONS: ShortcutDefinition[] = [
scope: "global",
defaultShortcut: "Meta+W",
},
{
id: "closeOtherTabs",
labelKey: "contextMenu.closeOtherTabs",
scope: "global",
defaultShortcut: closeOtherTabsDefaultShortcut(),
},
{
id: "focusSearch",
labelKey: "settings.shortcutFocusSearch",
@@ -389,7 +409,13 @@ export function normalizeShortcutSettings(settings?: Partial<ShortcutSettings>):
return Object.fromEntries(
SHORTCUT_DEFINITIONS.map((definition) => {
const configuredValue = settings?.[definition.id];
const configured = typeof configuredValue === "string" ? configuredValue : definition.defaultShortcut;
let configured = typeof configuredValue === "string" ? configuredValue : definition.defaultShortcut;
// 云同步会把另一平台的默认值当作显式配置带过来(macOS 的 Alt+Mod+W 到
// Windows 上会还原成 Ctrl+Alt+W)。凡是平台默认集合内的值都视为"未
// 自定义",按本机平台重新解析;用户真正自定义的其他组合原样保留
if (definition.id === "closeOtherTabs" && CLOSE_OTHER_TABS_PLATFORM_DEFAULTS.has(configured)) {
configured = definition.defaultShortcut;
}
const normalized = definition.inputKind === "modifier-only" ? normalizeModifierOnlyShortcut(configured, definition.defaultShortcut) : configured;
return [definition.id, normalized];
}),