1- import { useState , useEffect } from "react" ;
1+ import { useState , useEffect , useCallback , useRef } from "react" ;
22import type { ClipboardItem as ClipboardItemType } from "../../models/ClipboardItem" ;
33import HistoryItemCard from "../components/ClipboardItem" ;
44import "./ClipboardHistory.css" ;
@@ -11,6 +11,9 @@ export default function ClipboardHistory({}: ClipboardHistoryProps) {
1111 const [ hasMore , setHasMore ] = useState ( true ) ;
1212 const [ searchQuery , setSearchQuery ] = useState ( "" ) ;
1313 const [ hasApiKey , setHasApiKey ] = useState ( false ) ;
14+ const [ selectedIndex , setSelectedIndex ] = useState ( 0 ) ;
15+ const listRef = useRef < HTMLDivElement > ( null ) ;
16+ const searchInputRef = useRef < HTMLInputElement > ( null ) ;
1417
1518 const performSearch = async ( ) => {
1619 console . log ( "Search triggered for:" , searchQuery ) ;
@@ -43,6 +46,74 @@ export default function ClipboardHistory({}: ClipboardHistoryProps) {
4346 }
4447 } ;
4548
49+ const copySelectedItem = useCallback ( async ( ) => {
50+ const item = history [ selectedIndex ] ;
51+ if ( ! item ) return ;
52+
53+ if ( item . type === "text" && item . text ) {
54+ await navigator . clipboard . writeText ( item . text ) ;
55+ } else if ( item . type === "image" && item . image ) {
56+ const response = await fetch ( item . image ) ;
57+ const blob = await response . blob ( ) ;
58+ await navigator . clipboard . write ( [
59+ new ( window as any ) . ClipboardItem ( { [ blob . type ] : blob } ) ,
60+ ] ) ;
61+ }
62+ } , [ history , selectedIndex ] ) ;
63+
64+ // Global keyboard navigation
65+ useEffect ( ( ) => {
66+ const handleGlobalKeyDown = ( e : KeyboardEvent ) => {
67+ // Don't handle if typing in search input
68+ if ( document . activeElement === searchInputRef . current && e . key !== "Escape" && e . key !== "ArrowDown" && e . key !== "ArrowUp" ) {
69+ return ;
70+ }
71+
72+ switch ( e . key ) {
73+ case "ArrowDown" :
74+ e . preventDefault ( ) ;
75+ setSelectedIndex ( ( prev ) => Math . min ( prev + 1 , history . length - 1 ) ) ;
76+ break ;
77+ case "ArrowUp" :
78+ e . preventDefault ( ) ;
79+ setSelectedIndex ( ( prev ) => Math . max ( prev - 1 , 0 ) ) ;
80+ break ;
81+ case "Enter" :
82+ case "c" :
83+ case "C" :
84+ if ( document . activeElement !== searchInputRef . current ) {
85+ e . preventDefault ( ) ;
86+ copySelectedItem ( ) ;
87+ }
88+ break ;
89+ case "Escape" :
90+ e . preventDefault ( ) ;
91+ searchInputRef . current ?. blur ( ) ;
92+ break ;
93+ case "/" :
94+ if ( document . activeElement !== searchInputRef . current ) {
95+ e . preventDefault ( ) ;
96+ searchInputRef . current ?. focus ( ) ;
97+ }
98+ break ;
99+ }
100+ } ;
101+
102+ window . addEventListener ( "keydown" , handleGlobalKeyDown ) ;
103+ return ( ) => window . removeEventListener ( "keydown" , handleGlobalKeyDown ) ;
104+ } , [ history . length , copySelectedItem ] ) ;
105+
106+ // Scroll selected item into view
107+ useEffect ( ( ) => {
108+ const selectedElement = listRef . current ?. querySelector ( `[data-index="${ selectedIndex } "]` ) ;
109+ selectedElement ?. scrollIntoView ( { block : "nearest" , behavior : "smooth" } ) ;
110+ } , [ selectedIndex ] ) ;
111+
112+ // Reset selection when history changes
113+ useEffect ( ( ) => {
114+ setSelectedIndex ( 0 ) ;
115+ } , [ history . length ] ) ;
116+
46117 useEffect ( ( ) => {
47118 window . electronAPI . getClipboardHistory ( ) . then ( ( items ) => {
48119 console . log ( `Initial history loaded: ${ items . length } items` ) ;
@@ -120,11 +191,12 @@ export default function ClipboardHistory({}: ClipboardHistoryProps) {
120191 />
121192 </ svg >
122193 < input
194+ ref = { searchInputRef }
123195 type = "text"
124196 className = "search-input"
125197 placeholder = {
126198 hasApiKey
127- ? "Search with AI..."
199+ ? "Search with AI... (press / to focus) "
128200 : "Configure OpenAI API key in settings..."
129201 }
130202 value = { searchQuery }
@@ -156,7 +228,7 @@ export default function ClipboardHistory({}: ClipboardHistoryProps) {
156228 ) }
157229 </ div >
158230 { hasApiKey ? (
159- < p className = "search-hint" > Press Enter to search</ p >
231+ < p className = "search-hint" > Press Enter to search • ↑↓ Navigate • Enter/C to copy • / to search </ p >
160232 ) : (
161233 < p className = "search-warning" >
162234 OpenAI API key required. Configure in Settings to enable semantic
@@ -172,12 +244,14 @@ export default function ClipboardHistory({}: ClipboardHistoryProps) {
172244 </ div >
173245 ) : (
174246 < >
175- < div className = "history-list" >
247+ < div className = "history-list" ref = { listRef } >
176248 { history . map ( ( item , index ) => (
177249 < HistoryItemCard
178250 key = { item . id || index }
179251 item = { item }
180252 index = { index }
253+ isSelected = { index === selectedIndex }
254+ onSelect = { ( ) => setSelectedIndex ( index ) }
181255 />
182256 ) ) }
183257 </ div >
0 commit comments