diff --git a/apps/desktop/src/renderer/src/App.tsx b/apps/desktop/src/renderer/src/App.tsx index d8d4966..454c3cc 100644 --- a/apps/desktop/src/renderer/src/App.tsx +++ b/apps/desktop/src/renderer/src/App.tsx @@ -33,6 +33,7 @@ export function App(): JSX.Element { const setUpdateInfo = useAppStore((s) => s.setUpdateInfo); const saveOrPrompt = useAppStore((s) => s.saveOrPrompt); const focusUrl = useAppStore((s) => s.focusUrl); + const focusSearch = useAppStore((s) => s.focusSearch); const toggleHiddenRequest = useAppStore((s) => s.toggleHiddenRequest); const tabs = useAppStore((s) => s.tabs); const isRepo = useAppStore((s) => s.gitStatus?.isRepo === true); @@ -88,6 +89,17 @@ export function App(): JSX.Element { handler: () => activeTabId && duplicateTab(activeTabId), }, { combo: 'mod+l', description: 'Focus URL bar', handler: () => focusUrl() }, + { + combo: 'mod+f', + description: 'Find in response', + handler: () => { + const state = useAppStore.getState(); + const tab = state.tabs.find((t) => t.id === state.activeTabId); + if (tab?.execution.status === 'success') { + focusSearch(); + } + }, + }, { combo: 'mod+enter', description: 'Send / cancel request', @@ -137,6 +149,7 @@ export function App(): JSX.Element { activeTabId, saveOrPrompt, focusUrl, + focusSearch, toggleHiddenRequest, tabs, isRepo, diff --git a/apps/desktop/src/renderer/src/components/ResponseViewer.tsx b/apps/desktop/src/renderer/src/components/ResponseViewer.tsx index 40b984b..4ce2d6a 100644 --- a/apps/desktop/src/renderer/src/components/ResponseViewer.tsx +++ b/apps/desktop/src/renderer/src/components/ResponseViewer.tsx @@ -46,6 +46,12 @@ export function ResponseViewer(): JSX.Element { }); const [tab, setTab] = useState('body'); + const focusSearchTick = useAppStore((s) => s.focusSearchTick); + useEffect(() => { + if (focusSearchTick === 0) return; + setTab('body'); + }, [focusSearchTick]); + if (!execution) { return ( @@ -255,8 +261,16 @@ function BodyPanel({ response }: { response: ExecutedResponse }): JSX.Element { }, [matches.length]); const searchable_supported = mode === 'raw' || mode === 'pretty'; + const searchInputRef = useRef(null); const activeMatchRef = useRef(null); + const focusSearchTick = useAppStore((s) => s.focusSearchTick); + useEffect(() => { + if (focusSearchTick === 0) return; + searchInputRef.current?.focus(); + searchInputRef.current?.select(); + }, [focusSearchTick]); + useEffect(() => { if (activeMatchRef.current) { activeMatchRef.current.scrollIntoView({ @@ -280,6 +294,7 @@ function BodyPanel({ response }: { response: ExecutedResponse }): JSX.Element { {searchable_supported && ( ; value: string; onChange: (value: string) => void; matchCount: number; @@ -343,6 +360,7 @@ function SearchBox({ return (
void; + // Bumped by ⌘F; ResponseViewer watches and focuses+selects the search input. + focusSearchTick: number; + focusSearch: () => void; + // Ticks bumped by the command palette to open dialogs owned by RequestBuilder. importCurlTick: number; openImportCurl: () => void; @@ -537,6 +541,7 @@ export const useAppStore = create((set, get) => { setSidebarView: (view) => set({ sidebarView: view }), saveDialogOpen: false, focusUrlTick: 0, + focusSearchTick: 0, importCurlTick: 0, loadTestTick: 0, revealInSidebarTick: 0, @@ -582,6 +587,7 @@ export const useAppStore = create((set, get) => { }, focusUrl: () => set({ focusUrlTick: get().focusUrlTick + 1 }), + focusSearch: () => set({ focusSearchTick: get().focusSearchTick + 1 }), openImportCurl: () => set({ importCurlTick: get().importCurlTick + 1 }), openLoadTest: () => set({ loadTestTick: get().loadTestTick + 1 }),