1- import { useEffect , useRef , useCallback , useMemo } from 'react' ;
1+ import { useState , useEffect , useRef , useCallback , useMemo } from 'react' ;
22import { useParams } from 'react-router-dom' ;
33import { useBoardStore } from '../stores/useBoardStore' ;
44import { useWebSocket } from '../hooks/useWebSocket' ;
@@ -28,6 +28,19 @@ export default function Board() {
2828 // --- Store ---
2929 const store = useBoardStore ( ) ;
3030
31+ // --- Filter and Sort ---
32+ const [ filter , setFilter ] = useState < 'all' | 'image' | 'other' > ( 'all' ) ;
33+
34+ const filteredItems = useMemo ( ( ) => {
35+ if ( filter === 'image' ) {
36+ return store . items . filter ( item => item . type === 'image' ) ;
37+ }
38+ if ( filter === 'other' ) {
39+ return store . items . filter ( item => item . type !== 'image' ) ;
40+ }
41+ return store . items ;
42+ } , [ store . items , filter ] ) ;
43+
3144 // --- Sync roomId into store ---
3245 useEffect ( ( ) => {
3346 store . setRoomId ( roomId || null ) ;
@@ -174,8 +187,48 @@ export default function Board() {
174187 { /* Main Content Area */ }
175188 < div className = "flex-1 pt-[120px] flex overflow-hidden" >
176189 < main className = "flex-1 overflow-auto p-4 sm:p-6 custom-scrollbar relative z-10 w-full h-full cursor-default" >
190+ { /* Filter Bar */ }
191+ < div className = "flex items-center justify-between mb-6 max-w-[1920px] mx-auto px-1" >
192+ < div className = "flex bg-white/80 dark:bg-slate-800/80 backdrop-blur-md p-1 rounded-xl border border-slate-200/50 dark:border-slate-700/50 shadow-sm" >
193+ < button
194+ onClick = { ( ) => setFilter ( 'all' ) }
195+ className = { `px-4 py-1.5 rounded-lg text-xs font-medium transition-all duration-200 cursor-pointer ${
196+ filter === 'all'
197+ ? 'bg-indigo-600 text-white shadow-sm'
198+ : 'text-slate-500 dark:text-slate-400 hover:text-slate-800 dark:hover:text-slate-200'
199+ } `}
200+ >
201+ { t ( 'filterAll' ) }
202+ </ button >
203+ < button
204+ onClick = { ( ) => setFilter ( 'image' ) }
205+ className = { `px-4 py-1.5 rounded-lg text-xs font-medium transition-all duration-200 cursor-pointer ${
206+ filter === 'image'
207+ ? 'bg-indigo-600 text-white shadow-sm'
208+ : 'text-slate-500 dark:text-slate-400 hover:text-slate-800 dark:hover:text-slate-200'
209+ } `}
210+ >
211+ { t ( 'filterImages' ) }
212+ </ button >
213+ < button
214+ onClick = { ( ) => setFilter ( 'other' ) }
215+ className = { `px-4 py-1.5 rounded-lg text-xs font-medium transition-all duration-200 cursor-pointer ${
216+ filter === 'other'
217+ ? 'bg-indigo-600 text-white shadow-sm'
218+ : 'text-slate-500 dark:text-slate-400 hover:text-slate-800 dark:hover:text-slate-200'
219+ } `}
220+ >
221+ { t ( 'filterOther' ) }
222+ </ button >
223+ </ div >
224+
225+ < div className = "text-xs font-medium text-slate-400 dark:text-slate-500 bg-white/40 dark:bg-slate-800/40 px-3 py-1.5 rounded-lg border border-slate-200/30 dark:border-slate-700/30 select-none" >
226+ { filteredItems . length }
227+ </ div >
228+ </ div >
229+
177230 < CardGrid
178- items = { store . items }
231+ items = { filteredItems }
179232 copiedId = { store . copiedId }
180233 isReadOnly = { isReadOnly }
181234 onCopy = { ( content , id ) => store . handleCopy ( content , showToast , t , id ) }
@@ -189,7 +242,13 @@ export default function Board() {
189242 const res = await fetch ( url ) ;
190243 const blob = await res . blob ( ) ;
191244 const mimeType = blob . type . startsWith ( 'image/' ) ? blob . type : 'image/png' ;
192- await navigator . clipboard . write ( [ new ClipboardItem ( { [ mimeType ] : blob } ) ] ) ;
245+ const textBlob = new Blob ( [ absoluteUrl ] , { type : 'text/plain' } ) ;
246+ await navigator . clipboard . write ( [
247+ new ClipboardItem ( {
248+ [ mimeType ] : blob ,
249+ 'text/plain' : textBlob
250+ } )
251+ ] ) ;
193252 showToast ( t ( 'copied' ) , 'success' ) ;
194253 } catch ( err ) {
195254 console . warn ( 'Failed to copy image blob, copying URL instead:' , err ) ;
0 commit comments