mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-21 08:13:34 +00:00
fix: 修复 Windows 快捷键显示异常 (#2599)
Co-authored-by: staff <staff@qimaos-MacBook-Pro.local>
This commit is contained in:
@@ -2350,8 +2350,8 @@ onUnmounted(cleanupPreviewEditor);
|
||||
readonly
|
||||
:aria-invalid="shortcutConflicts.includes(definition.id)"
|
||||
:placeholder="t('settings.shortcutPressShortcut')"
|
||||
class="h-7 w-auto min-w-12 max-w-32 shrink-0 cursor-default rounded-[6px] border border-transparent bg-background px-2.5 text-center font-mono text-[13px] font-semibold text-foreground/75 shadow-inner outline-none selection:bg-transparent placeholder:text-muted-foreground aria-invalid:border-destructive/70 aria-invalid:text-destructive aria-invalid:ring-destructive/20"
|
||||
:class="editingShortcutId === definition.id ? 'max-w-44 cursor-text border-border/80 bg-background text-left text-foreground shadow-none focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/35' : ''"
|
||||
class="h-7 w-auto min-w-12 max-w-64 shrink-0 cursor-default rounded-[6px] border border-transparent bg-background px-2.5 text-center font-mono text-[13px] font-semibold text-foreground/75 shadow-inner outline-none selection:bg-transparent placeholder:text-muted-foreground aria-invalid:border-destructive/70 aria-invalid:text-destructive aria-invalid:ring-destructive/20"
|
||||
:class="editingShortcutId === definition.id ? 'max-w-64 cursor-text border-border/80 bg-background text-left text-foreground shadow-none focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/35' : ''"
|
||||
@keydown="(event: KeyboardEvent) => onShortcutKeydown(definition.id, event)"
|
||||
/>
|
||||
<Button
|
||||
@@ -3175,8 +3175,8 @@ onUnmounted(cleanupPreviewEditor);
|
||||
readonly
|
||||
:aria-invalid="shortcutConflicts.includes(definition.id)"
|
||||
:placeholder="t('settings.shortcutPressShortcut')"
|
||||
class="h-7 w-auto min-w-12 max-w-32 shrink-0 cursor-default rounded-[6px] border border-transparent bg-muted px-2.5 text-center font-mono text-[13px] font-semibold text-foreground/75 shadow-inner outline-none selection:bg-transparent placeholder:text-muted-foreground aria-invalid:border-destructive/70 aria-invalid:text-destructive aria-invalid:ring-destructive/20"
|
||||
:class="editingShortcutId === definition.id ? 'max-w-44 cursor-text border-border/80 bg-background text-left text-foreground shadow-none focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/35' : ''"
|
||||
class="h-7 w-auto min-w-12 max-w-64 shrink-0 cursor-default rounded-[6px] border border-transparent bg-muted px-2.5 text-center font-mono text-[13px] font-semibold text-foreground/75 shadow-inner outline-none selection:bg-transparent placeholder:text-muted-foreground aria-invalid:border-destructive/70 aria-invalid:text-destructive aria-invalid:ring-destructive/20"
|
||||
:class="editingShortcutId === definition.id ? 'max-w-64 cursor-text border-border/80 bg-background text-left text-foreground shadow-none focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/35' : ''"
|
||||
@keydown="(event: KeyboardEvent) => onShortcutKeydown(definition.id, event)"
|
||||
/>
|
||||
<Button
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { eventToShortcut, matchesShortcut } from "@/lib/editor/keyboardShortcuts";
|
||||
|
||||
describe("keyboard shortcut matching", () => {
|
||||
it("records the plus key without losing it to the separator", () => {
|
||||
expect(eventToShortcut({ key: "+", ctrlKey: true })).toBe("Mod+Plus");
|
||||
expect(eventToShortcut({ key: "+", ctrlKey: true, shiftKey: true })).toBe("Shift+Mod+Plus");
|
||||
});
|
||||
|
||||
it("matches canonical plus-key shortcuts", () => {
|
||||
expect(matchesShortcut({ key: "+", ctrlKey: true }, "Mod+Plus")).toBe(true);
|
||||
expect(matchesShortcut({ key: "+", ctrlKey: true, shiftKey: true }, "Shift+Mod+Plus")).toBe(true);
|
||||
});
|
||||
|
||||
it("matches legacy plus-key shortcuts saved with plus as a separator", () => {
|
||||
expect(matchesShortcut({ key: "+", ctrlKey: true }, "Mod++")).toBe(true);
|
||||
expect(matchesShortcut({ key: "+", ctrlKey: true, shiftKey: true }, "Shift+Mod++")).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -19,4 +19,19 @@ describe("shortcut display", () => {
|
||||
expect(formatShortcutDisplay("Shift+Alt+ArrowUp", "MacIntel")).toBe("⇧ ⌥ ↑");
|
||||
expect(formatShortcutDisplay("Mod+Delete", "MacIntel")).toBe("⌘ ⌦");
|
||||
});
|
||||
|
||||
it("orders Ctrl before Shift on Windows", () => {
|
||||
expect(formatShortcutDisplay("Shift+Mod+F", "Win32")).toBe("Ctrl + Shift + F");
|
||||
expect(formatShortcutDisplay("Shift+Mod+K", "Win32")).toBe("Ctrl + Shift + K");
|
||||
expect(formatShortcutDisplay("Shift+Mod+Z", "Win32")).toBe("Ctrl + Shift + Z");
|
||||
});
|
||||
|
||||
it("keeps macOS modifier order and glyphs", () => {
|
||||
expect(formatShortcutDisplay("Shift+Mod+F", "MacIntel")).toBe("⇧ ⌘ F");
|
||||
});
|
||||
|
||||
it("displays canonical and legacy plus-key shortcuts", () => {
|
||||
expect(formatShortcutDisplay("Mod+Plus", "Win32")).toBe("Ctrl + +");
|
||||
expect(formatShortcutDisplay("Shift+Mod++", "Win32")).toBe("Ctrl + Shift + +");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { DEFAULT_SHORTCUT_SETTINGS, SHORTCUT_DEFINITIONS, findShortcutConflict, normalizeShortcutSettings, type ShortcutActionId } from "@/lib/editor/shortcutRegistry";
|
||||
import { DEFAULT_SHORTCUT_SETTINGS, SHORTCUT_DEFINITIONS, findShortcutConflict, formatShortcut, normalizeShortcutSettings, shortcutToCodeMirrorKey, type ShortcutActionId } from "@/lib/editor/shortcutRegistry";
|
||||
|
||||
describe("shortcutRegistry editor actions", () => {
|
||||
const formatterEditorActionIds: ShortcutActionId[] = ["formatSql", "indentMore", "indentLess", "duplicateLine", "deleteLine", "moveLineUp", "moveLineDown", "copyLineUp", "copyLineDown", "undo", "redo", "selectAll", "uppercaseSelection", "lowercaseSelection"];
|
||||
@@ -61,4 +61,13 @@ describe("shortcutRegistry editor actions", () => {
|
||||
expect(findShortcutConflict("copySidebarSelection", shortcuts.copySidebarSelection, shortcuts)).toBe("editSidebarConnection");
|
||||
expect(findShortcutConflict("copyCurrentRow", shortcuts.copyCurrentRow, shortcuts)).toBe(null);
|
||||
});
|
||||
|
||||
it("formats Ctrl before Shift on Windows", () => {
|
||||
expect(formatShortcut("Shift+Mod+F", "Win32")).toBe("Ctrl+Shift+F");
|
||||
});
|
||||
|
||||
it("converts plus-key shortcuts for CodeMirror keymaps", () => {
|
||||
expect(shortcutToCodeMirrorKey("Mod+Plus")).toBe("Mod-+");
|
||||
expect(shortcutToCodeMirrorKey("Shift+Mod++")).toBe("Shift-Mod-+");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { parseShortcutParts } from "@/lib/editor/shortcutDisplay";
|
||||
import { normalizeShortcutSettings, type ShortcutActionId, type ShortcutSettings } from "@/lib/editor/shortcutRegistry";
|
||||
|
||||
export interface ShortcutLikeEvent {
|
||||
@@ -11,11 +12,13 @@ export interface ShortcutLikeEvent {
|
||||
|
||||
function normalizeKey(key: string): string {
|
||||
if (key === " ") return "Space";
|
||||
if (key === "+" || key === "Plus") return "Plus";
|
||||
return key.length === 1 ? key.toLowerCase() : key;
|
||||
}
|
||||
|
||||
function shortcutKeyName(key: string): string | null {
|
||||
if (key === " ") return "Space";
|
||||
if (key === "+") return "Plus";
|
||||
if (["Control", "Meta", "Shift", "Alt"].includes(key)) return null;
|
||||
if (key.length === 1) return key.toUpperCase();
|
||||
return key;
|
||||
@@ -28,7 +31,7 @@ export function eventToShortcut(event: ShortcutLikeEvent): string | null {
|
||||
if (!key) return null;
|
||||
|
||||
const hasModifier = !!event.metaKey || !!event.ctrlKey || !!event.altKey || !!event.shiftKey;
|
||||
if (!hasModifier && key.length === 1) return null;
|
||||
if (!hasModifier && event.key.length === 1 && event.key !== " ") return null;
|
||||
|
||||
const parts: string[] = [];
|
||||
if (event.shiftKey) parts.push("Shift");
|
||||
@@ -40,7 +43,7 @@ export function eventToShortcut(event: ShortcutLikeEvent): string | null {
|
||||
|
||||
export function matchesShortcut(event: ShortcutLikeEvent, shortcut: string): boolean {
|
||||
if (event.isComposing || !shortcut) return false;
|
||||
const parts = shortcut.split("+");
|
||||
const parts = parseShortcutParts(shortcut);
|
||||
const key = parts[parts.length - 1] ?? "";
|
||||
const modifiers = new Set(parts.slice(0, -1));
|
||||
const usesMod = modifiers.has("Mod");
|
||||
|
||||
@@ -2,6 +2,47 @@ export function isMacShortcutPlatform(platform = globalThis.navigator?.platform
|
||||
return platform.toLowerCase().includes("mac");
|
||||
}
|
||||
|
||||
export function parseShortcutParts(shortcut?: string): string[] {
|
||||
if (!shortcut) return [];
|
||||
|
||||
if (shortcut.endsWith("++")) {
|
||||
const prefix = shortcut.slice(0, -2);
|
||||
return [...prefix.split("+").filter(Boolean), "Plus"];
|
||||
}
|
||||
|
||||
return shortcut.split("+").filter(Boolean);
|
||||
}
|
||||
|
||||
function shortcutDisplayOrder(parts: string[], platform = globalThis.navigator?.platform || ""): string[] {
|
||||
if (parts.length <= 2 || isMacShortcutPlatform(platform)) return parts;
|
||||
|
||||
const key = parts[parts.length - 1];
|
||||
const modifierOrder = new Map([
|
||||
["Mod", 0],
|
||||
["Ctrl", 0],
|
||||
["Control", 0],
|
||||
["Meta", 0],
|
||||
["Cmd", 0],
|
||||
["Shift", 1],
|
||||
["Alt", 2],
|
||||
]);
|
||||
const modifiers = parts.slice(0, -1);
|
||||
if (!modifiers.some((part) => modifierOrder.get(part) === 0)) return parts;
|
||||
|
||||
return [
|
||||
...[...modifiers].sort((a, b) => {
|
||||
const rankA = modifierOrder.get(a) ?? 99;
|
||||
const rankB = modifierOrder.get(b) ?? 99;
|
||||
return rankA - rankB;
|
||||
}),
|
||||
key,
|
||||
];
|
||||
}
|
||||
|
||||
export function shortcutDisplayParts(shortcut?: string, platform = globalThis.navigator?.platform || ""): string[] {
|
||||
return shortcutDisplayOrder(parseShortcutParts(shortcut), platform);
|
||||
}
|
||||
|
||||
export function shortcutKeyLabel(part: string, platform = globalThis.navigator?.platform || ""): string {
|
||||
const isMac = isMacShortcutPlatform(platform);
|
||||
if (part === "Mod") return isMac ? "⌘" : "Ctrl";
|
||||
@@ -19,16 +60,12 @@ export function shortcutKeyLabel(part: string, platform = globalThis.navigator?.
|
||||
if (part === "ArrowLeft") return "←";
|
||||
if (part === "ArrowRight") return "→";
|
||||
if (part === " ") return "Space";
|
||||
if (part === "Plus") return "+";
|
||||
return part.length === 1 ? part.toUpperCase() : part;
|
||||
}
|
||||
|
||||
export function shortcutDisplayKeys(shortcut?: string, platform = globalThis.navigator?.platform || ""): string[] {
|
||||
return (
|
||||
shortcut
|
||||
?.split("+")
|
||||
.filter(Boolean)
|
||||
.map((part) => shortcutKeyLabel(part, platform)) || []
|
||||
);
|
||||
return shortcutDisplayParts(shortcut, platform).map((part) => shortcutKeyLabel(part, platform));
|
||||
}
|
||||
|
||||
export function formatShortcutDisplay(shortcut: string, platform = globalThis.navigator?.platform || ""): string {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { parseShortcutParts, shortcutDisplayParts } from "@/lib/editor/shortcutDisplay";
|
||||
|
||||
export type ShortcutActionId =
|
||||
| "executeSql"
|
||||
| "formatSql"
|
||||
@@ -350,19 +352,19 @@ export function normalizeShortcutSettings(settings?: Partial<ShortcutSettings>):
|
||||
}
|
||||
|
||||
export function shortcutToCodeMirrorKey(shortcut: string): string {
|
||||
return shortcut
|
||||
.split("+")
|
||||
return parseShortcutParts(shortcut)
|
||||
.map((part) => (part.length === 1 ? part.toLowerCase() : part))
|
||||
.map((part) => (part === "Plus" ? "+" : part))
|
||||
.join("-");
|
||||
}
|
||||
|
||||
export function formatShortcut(shortcut: string, platform = globalThis.navigator?.platform || ""): string {
|
||||
const isMac = platform.toLowerCase().includes("mac");
|
||||
return shortcut
|
||||
.split("+")
|
||||
return shortcutDisplayParts(shortcut, platform)
|
||||
.map((part) => {
|
||||
if (part === "Mod") return isMac ? "Cmd" : "Ctrl";
|
||||
if (part === "Meta") return isMac ? "Cmd" : "Meta";
|
||||
if (part === "Plus") return "+";
|
||||
return part;
|
||||
})
|
||||
.join("+");
|
||||
|
||||
Reference in New Issue
Block a user