diff --git a/plugins/file-manager/src/frontend/FileManagerToolbar.tsx b/plugins/file-manager/src/frontend/FileManagerToolbar.tsx index 46ed52212..f8ba3d64b 100644 --- a/plugins/file-manager/src/frontend/FileManagerToolbar.tsx +++ b/plugins/file-manager/src/frontend/FileManagerToolbar.tsx @@ -232,6 +232,43 @@ export function FileManagerToolbar({ sidebarOpen = true, onToggleSidebar, }: FileManagerToolbarProps) { + const searchInputRef = useRef(null); + + useEffect(() => { + const handleSearchShortcut = (event: KeyboardEvent) => { + if ( + event.defaultPrevented || + !(event.ctrlKey || event.metaKey) || + event.altKey || + event.shiftKey || + event.key.toLowerCase() !== "f" + ) + return; + + const input = searchInputRef.current; + if (!input) return; + const visible = + typeof input.checkVisibility === "function" + ? input.checkVisibility({ visibilityProperty: true }) + : input.offsetParent !== null; + if (!visible) return; + const active = document.activeElement; + if ( + active !== input && + active?.closest( + 'input, textarea, [contenteditable="true"], [role="dialog"]', + ) + ) + return; + + event.preventDefault(); + input.focus(); + input.select(); + }; + document.addEventListener("keydown", handleSearchShortcut); + return () => document.removeEventListener("keydown", handleSearchShortcut); + }, []); + return (
@@ -325,6 +362,7 @@ export function FileManagerToolbar({
setSearchQuery(e.target.value)} diff --git a/plugins/file-manager/tests/frontend/FileManagerToolbar.test.tsx b/plugins/file-manager/tests/frontend/FileManagerToolbar.test.tsx new file mode 100644 index 000000000..91f87a994 --- /dev/null +++ b/plugins/file-manager/tests/frontend/FileManagerToolbar.test.tsx @@ -0,0 +1,101 @@ +import React from "react"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import { cleanup, fireEvent, render, screen } from "@testing-library/react"; +import "@testing-library/jest-dom/vitest"; +import { FileManagerToolbar } from "../../src/frontend/FileManagerToolbar"; + +const props: React.ComponentProps = { + t: (key) => key, + currentPath: "/home/alice", + navIndex: 0, + navHistoryLength: 1, + isLoading: false, + sshSessionId: "session", + selectedFiles: [], + searchQuery: "report", + setSearchQuery: vi.fn(), + viewMode: "list", + setViewMode: vi.fn(), + density: "comfortable", + setDensity: vi.fn(), + sortBy: "name", + setSortBy: vi.fn(), + sortOrder: "asc", + setSortOrder: vi.fn(), + setMobileSidebarOpen: vi.fn(), + goBack: vi.fn(), + goForward: vi.fn(), + goUp: vi.fn(), + navigateTo: vi.fn(), + handleRefreshDirectory: vi.fn(), + handleDeleteFiles: vi.fn(), + handleCopyFiles: vi.fn(), + handleFilesDropped: vi.fn(), + handleCreateNewFolder: vi.fn(), + handleCreateNewFile: vi.fn(), +}; + +function toolbar(visible = true) { + const view = render(); + const input = view.getByPlaceholderText( + "fileManager.searchFiles", + ) as HTMLInputElement; + Object.defineProperty(input, "checkVisibility", { value: () => visible }); + return { ...view, input }; +} + +afterEach(cleanup); + +describe("file search shortcut", () => { + it.each([{ ctrlKey: true }, { metaKey: true }])( + "focuses and selects the search query with %j", + (modifier) => { + const { input } = toolbar(); + expect(fireEvent.keyDown(document, { key: "f", ...modifier })).toBe( + false, + ); + expect(input).toHaveFocus(); + expect([input.selectionStart, input.selectionEnd]).toEqual([0, 6]); + }, + ); + + it("does not handle the shortcut in a hidden tab", () => { + const { input } = toolbar(false); + expect(fireEvent.keyDown(document, { key: "f", ctrlKey: true })).toBe(true); + expect(input).not.toHaveFocus(); + }); + + it("leaves editor and dialog shortcuts alone", () => { + const { input } = toolbar(); + render( + <> +