From 2013cdb98db8dca7d1eff7f85ecd87f54aa9cd93 Mon Sep 17 00:00:00 2001 From: aleksey-hoffman Date: Mon, 6 Jul 2026 13:13:21 +0300 Subject: [PATCH] Dev: fix copy current dir path shortcut throwing error --- src/modules/navigator/pages/navigator.vue | 22 ++++---- .../runtime/__tests__/shortcuts.test.ts | 51 +++++++++++++++++++ src/stores/runtime/shortcuts.ts | 45 +++++++++++++++- 3 files changed, 107 insertions(+), 11 deletions(-) diff --git a/src/modules/navigator/pages/navigator.vue b/src/modules/navigator/pages/navigator.vue index b1c99d30..3cb81eb6 100644 --- a/src/modules/navigator/pages/navigator.vue +++ b/src/modules/navigator/pages/navigator.vue @@ -600,14 +600,7 @@ function getNavigatorActionContext(currentDirectoryPath?: string) { ); } -function getPasteTargetPath(): string | undefined { - const currentPath = getActiveCurrentPath(); - const actionContext = getNavigatorActionContext(currentPath); - - if (actionContext.isBrowsingVirtualLocations) { - return actionContext.actionDirectoryPath ?? undefined; - } - +function getTabGroupFallbackPath(): string | undefined { if (isSplitView.value && activeTabId.value) { const activeTab = workspacesStore.currentTabGroup?.find( tab => tab.id === activeTabId.value, @@ -628,7 +621,18 @@ function getActiveCurrentPath(): string | undefined { return pane.currentPath; } - return getPasteTargetPath(); + return getTabGroupFallbackPath(); +} + +function getPasteTargetPath(): string | undefined { + const currentPath = getActiveCurrentPath(); + const actionContext = getNavigatorActionContext(currentPath); + + if (actionContext.isBrowsingVirtualLocations) { + return actionContext.actionDirectoryPath ?? undefined; + } + + return currentPath; } async function handleGlobalSearchOpenEntry(entry: DirEntry) { diff --git a/src/stores/runtime/__tests__/shortcuts.test.ts b/src/stores/runtime/__tests__/shortcuts.test.ts index a8d19e2f..5aba2ed1 100644 --- a/src/stores/runtime/__tests__/shortcuts.test.ts +++ b/src/stores/runtime/__tests__/shortcuts.test.ts @@ -13,6 +13,7 @@ const { executeCommandMock, extensionKeybindings, userSettingsStoreMock, + routerCurrentRouteMock, } = vi.hoisted(() => ({ extensionKeybindings: [] as Array<{ extensionId: string; @@ -34,6 +35,15 @@ const { }, setUserSettingsStorage: vi.fn(), }, + routerCurrentRouteMock: { + value: { name: 'navigator' as string | symbol | null | undefined }, + }, +})); + +vi.mock('@/router', () => ({ + default: { + currentRoute: routerCurrentRouteMock, + }, })); vi.mock('@/stores/storage/user-settings', () => ({ @@ -66,6 +76,7 @@ import { describe('shortcuts store', () => { beforeEach(() => { setActivePinia(createPinia()); + routerCurrentRouteMock.value = { name: 'navigator' }; extensionKeybindings.splice(0, extensionKeybindings.length); userSettingsStoreMock.userSettings = reactive({ shortcuts: {}, @@ -440,6 +451,46 @@ describe('shortcuts store', () => { expect(event.defaultPrevented).toBe(true); }); + it('does not run navigator-scoped shortcuts when not on the navigator route', async () => { + routerCurrentRouteMock.value = { name: 'home' }; + + const shortcutsStore = useShortcutsStore(); + const copyCurrentDirectoryPathHandler = vi.fn(); + + shortcutsStore.registerHandler('copyCurrentDirectoryPath', copyCurrentDirectoryPathHandler); + + const event = new KeyboardEvent('keydown', { + key: 'C', + code: 'KeyC', + ctrlKey: true, + shiftKey: true, + bubbles: true, + cancelable: true, + }); + + await expect(shortcutsStore.handleKeydown(event)).resolves.toBe(false); + expect(copyCurrentDirectoryPathHandler).not.toHaveBeenCalled(); + expect(event.defaultPrevented).toBe(true); + }); + + it('prevents Ctrl+Shift+C default behavior on non-navigator routes without a registered handler', async () => { + routerCurrentRouteMock.value = { name: 'home' }; + + const shortcutsStore = useShortcutsStore(); + + const event = new KeyboardEvent('keydown', { + key: 'C', + code: 'KeyC', + ctrlKey: true, + shiftKey: true, + bubbles: true, + cancelable: true, + }); + + await expect(shortcutsStore.handleKeydown(event)).resolves.toBe(false); + expect(event.defaultPrevented).toBe(true); + }); + it('matches default create-new item shortcuts', async () => { const shortcutsStore = useShortcutsStore(); const createNewFileHandler = vi.fn(); diff --git a/src/stores/runtime/shortcuts.ts b/src/stores/runtime/shortcuts.ts index 736c52fb..45c14d57 100644 --- a/src/stores/runtime/shortcuts.ts +++ b/src/stores/runtime/shortcuts.ts @@ -19,6 +19,7 @@ import { shortcutMainKeyDisplayLabel, } from '@/localization/shortcut-main-key-label'; import { isDialogOpened, isInputFieldActive } from '@/utils/dom-interaction-state'; +import router from '@/router'; export type { ShortcutId, ShortcutKeys, UserShortcuts }; export type { UserShortcutStoredValue, ShortcutUserAlternateChordSlots } from '@/types/user-settings'; @@ -1574,7 +1575,34 @@ export const useShortcutsStore = defineStore('shortcuts', () => { await persistShortcutUserAlternateChordSlots({}); } - function checkConditions(definition: ShortcutDefinition, registration?: HandlerRegistration): boolean { + function isShortcutScopeRouteActive(scope: ShortcutDefinition['scope']): boolean { + if (scope === 'global') { + return true; + } + + const routeName = router.currentRoute.value.name; + + switch (scope) { + case 'navigator': + return routeName === 'navigator'; + case 'settings': + return routeName === 'settings'; + default: { + const unexpectedScope: never = scope; + return unexpectedScope; + } + } + } + + function checkShortcutConditions( + definition: ShortcutDefinition, + registration?: HandlerRegistration, + options?: { ignoreRouteScope?: boolean }, + ): boolean { + if (!options?.ignoreRouteScope && !isShortcutScopeRouteActive(definition.scope)) { + return false; + } + const { conditions } = definition; if (conditions.inputFieldIsActive !== undefined) { @@ -1598,6 +1626,19 @@ export const useShortcutsStore = defineStore('shortcuts', () => { return true; } + function checkConditions(definition: ShortcutDefinition, registration?: HandlerRegistration): boolean { + return checkShortcutConditions(definition, registration); + } + + function canPreventDefaultForShortcut(shortcutId: ShortcutId): boolean { + const definition = getShortcutDefinition(shortcutId); + if (!definition) return false; + + const registration = handlers.value.get(shortcutId); + + return checkShortcutConditions(definition, registration, { ignoreRouteScope: true }); + } + function registerHandler( shortcutId: ShortcutId, handler: ShortcutHandler, @@ -1808,7 +1849,7 @@ export const useShortcutsStore = defineStore('shortcuts', () => { } const shouldPreventDefaultEarly = matchingShortcutIds.some(shortcutId => - EARLY_PREVENT_DEFAULT_SHORTCUT_IDS.has(shortcutId) && canCallShortcutHandler(shortcutId), + EARLY_PREVENT_DEFAULT_SHORTCUT_IDS.has(shortcutId) && canPreventDefaultForShortcut(shortcutId), ); if (shouldPreventDefaultEarly) {