fix(shortcuts): allow clearing keyboard shortcuts in settings

This commit is contained in:
t8y2 2026-06-10 10:25:06 +08:00
parent ed6d275ca2
commit 51fe5de98b
9 changed files with 27 additions and 4 deletions

View File

@ -493,6 +493,7 @@ function onShortcutKeydown(actionId: ShortcutActionId, event: KeyboardEvent) {
}
function formatShortcutPill(shortcut: string): string {
if (!shortcut) return "—";
const isMac = globalThis.navigator?.platform?.toLowerCase().includes("mac") ?? false;
return shortcut
.split("+")
@ -539,6 +540,10 @@ function resetShortcut(actionId: ShortcutActionId) {
editShortcuts.value = { ...editShortcuts.value, [actionId]: definition.defaultShortcut };
}
function clearShortcut(actionId: ShortcutActionId) {
editShortcuts.value = { ...editShortcuts.value, [actionId]: "" };
}
function setAppLayout(value: "separated" | "classic") {
editAppLayout.value = value;
}
@ -1746,6 +1751,17 @@ watch(
>
<RotateCcw class="h-4 w-4" />
</Button>
<Button
v-if="editingShortcutId !== definition.id && editShortcuts[definition.id]"
type="button"
variant="ghost"
size="icon"
class="h-7 w-7 shrink-0 text-muted-foreground opacity-0 transition-opacity hover:text-destructive focus-visible:opacity-100 group-hover:opacity-100"
:aria-label="t('settings.shortcutClear')"
@click="clearShortcut(definition.id)"
>
<X class="h-4 w-4" />
</Button>
</div>
<p v-if="shortcutConflicts.includes(definition.id)" class="text-xs text-destructive">
{{ t("settings.shortcutConflict") }}

View File

@ -1922,6 +1922,7 @@ export default {
shortcutScopeSearch: "Search fields",
shortcutPressShortcut: "Press shortcut",
shortcutConflict: "This shortcut conflicts with another action in the same scope.",
shortcutClear: "Clear shortcut",
preview: "Live Preview",
customTheme: "Custom Theme",
customThemeConfigure: "Configure Custom Theme",

View File

@ -1701,6 +1701,7 @@ export default {
shortcutScopeSearch: "Campos de búsqueda",
shortcutPressShortcut: "Presiona un atajo",
shortcutConflict: "Este atajo entra en conflicto con otra acción del mismo ámbito.",
shortcutClear: "Borrar atajo",
preview: "Vista previa en tiempo real",
customTheme: "Tema personalizado",
customThemeConfigure: "Configurar tema personalizado",

View File

@ -1821,6 +1821,7 @@ export default {
shortcutScopeSearch: "Cerca campi",
shortcutPressShortcut: "Premi scorciatoia",
shortcutConflict: "Questa scorciatoia è in conflitto con un'altra azione nello stesso ambito.",
shortcutClear: "Cancella scorciatoia",
preview: "Anteprima in tempo reale",
customTheme: "Tema personalizzato",
customThemeConfigure: "Configura tema personalizzato",

View File

@ -1821,6 +1821,7 @@ export default {
shortcutScopeSearch: "Campos de pesquisa",
shortcutPressShortcut: "Pressione o atalho",
shortcutConflict: "Este atalho conflita com outra ação no mesmo escopo.",
shortcutClear: "Limpar atalho",
preview: "Pré-visualização ao vivo",
customTheme: "Tema personalizado",
customThemeConfigure: "Configurar tema personalizado",

View File

@ -1921,6 +1921,7 @@ export default {
shortcutScopeSearch: "搜索框",
shortcutPressShortcut: "按下快捷键",
shortcutConflict: "这个快捷键与同一作用域内的其他操作冲突。",
shortcutClear: "清除快捷键",
preview: "实时预览",
customTheme: "自定义主题",
customThemeConfigure: "自定义主题配置",

View File

@ -1793,6 +1793,7 @@ export default {
shortcutScopeSearch: "搜尋框",
shortcutPressShortcut: "按下快速鍵",
shortcutConflict: "這個快速鍵與同一作用域內的其他操作衝突。",
shortcutClear: "清除快速鍵",
preview: "即時預覽",
customTheme: "自定義主題",
customThemeConfigure: "自定義主題配置",

View File

@ -1,4 +1,4 @@
import { DEFAULT_SHORTCUT_SETTINGS, normalizeShortcutSettings, type ShortcutActionId, type ShortcutSettings } from "@/lib/shortcutRegistry";
import { normalizeShortcutSettings, type ShortcutActionId, type ShortcutSettings } from "@/lib/shortcutRegistry";
export interface ShortcutLikeEvent {
key: string;
@ -39,7 +39,7 @@ export function eventToShortcut(event: ShortcutLikeEvent): string | null {
}
export function matchesShortcut(event: ShortcutLikeEvent, shortcut: string): boolean {
if (event.isComposing) return false;
if (event.isComposing || !shortcut) return false;
const parts = shortcut.split("+");
const key = parts[parts.length - 1] ?? "";
const modifiers = new Set(parts.slice(0, -1));
@ -60,7 +60,7 @@ export function matchesShortcut(event: ShortcutLikeEvent, shortcut: string): boo
}
function actionShortcut(actionId: ShortcutActionId, shortcuts?: Partial<ShortcutSettings>): string {
return normalizeShortcutSettings(shortcuts)[actionId] || DEFAULT_SHORTCUT_SETTINGS[actionId];
return normalizeShortcutSettings(shortcuts)[actionId];
}
export function isExecuteSqlShortcut(event: ShortcutLikeEvent, shortcuts?: Partial<ShortcutSettings>): boolean {

View File

@ -143,7 +143,7 @@ export const SHORTCUT_DEFINITIONS: ShortcutDefinition[] = [
export const DEFAULT_SHORTCUT_SETTINGS: ShortcutSettings = Object.fromEntries(SHORTCUT_DEFINITIONS.map((definition) => [definition.id, definition.defaultShortcut])) as ShortcutSettings;
export function normalizeShortcutSettings(settings?: Partial<ShortcutSettings>): ShortcutSettings {
return Object.fromEntries(SHORTCUT_DEFINITIONS.map((definition) => [definition.id, typeof settings?.[definition.id] === "string" && settings[definition.id]?.trim() ? settings[definition.id] : definition.defaultShortcut])) as ShortcutSettings;
return Object.fromEntries(SHORTCUT_DEFINITIONS.map((definition) => [definition.id, typeof settings?.[definition.id] === "string" ? settings[definition.id] : definition.defaultShortcut])) as ShortcutSettings;
}
export function shortcutToCodeMirrorKey(shortcut: string): string {
@ -166,6 +166,7 @@ export function formatShortcut(shortcut: string, platform = globalThis.navigator
}
export function findShortcutConflict(actionId: ShortcutActionId, shortcut: string, shortcuts: ShortcutSettings): ShortcutActionId | null {
if (!shortcut) return null;
const definition = SHORTCUT_DEFINITIONS.find((item) => item.id === actionId);
if (!definition) return null;