@@ -6,6 +6,7 @@ export default function Browser() {
66 const [ tabs , setTabs ] = useState < Tab [ ] > ( [ { id : 1 , title : "Tab 1" , url : "about:blank" , active : true , reloadKey : 0 } ] ) ;
77 const [ url , setUrl ] = useState ( "about:blank" ) ;
88 const [ favicons , setFavicons ] = useState < { [ key : number ] : string } > ( { } ) ;
9+ const [ bookmarks , setBookmarks ] = useState < Array < { Title : string ; url : string ; favicon ?: string } > > ( [ ] ) ;
910 const activeTab = useMemo ( ( ) => tabs . find ( ( tab ) => tab . active ) , [ tabs ] ) ;
1011 const iframeRefs = useRef < { [ key : number ] : HTMLIFrameElement | null } > ( { } ) ;
1112
@@ -22,6 +23,13 @@ export default function Browser() {
2223
2324 setTabs ( ( prev ) => prev . map ( ( tab ) => ( { ...tab , url : firstTabUrl } ) ) ) ;
2425 setUrl ( firstTabUrl ) ;
26+
27+ try {
28+ const savedBookmarks = JSON . parse ( localStorage . getItem ( "bookmarks" ) || "[]" ) ;
29+ setBookmarks ( savedBookmarks ) ;
30+ } catch ( error ) {
31+ console . warn ( "Failed to load bookmarks:" , error ) ;
32+ }
2533 } , [ ] ) ;
2634
2735 useEffect ( ( ) => {
@@ -52,24 +60,19 @@ export default function Browser() {
5260
5361 const iframeDoc = iframe . contentWindow ?. document ;
5462 if ( iframeDoc ) {
55- const faviconLink =
56- iframeDoc . querySelector < HTMLLinkElement > ( 'link[rel="icon"]' ) ||
57- iframeDoc . querySelector < HTMLLinkElement > ( 'link[rel="shortcut icon"]' ) ||
58- iframeDoc . querySelector < HTMLLinkElement > ( 'link[rel="apple-touch-icon"]' ) ;
59-
63+ const faviconLink = iframeDoc . querySelector < HTMLLinkElement > ( 'link[rel="icon"]' ) || iframeDoc . querySelector < HTMLLinkElement > ( 'link[rel="shortcut icon"]' ) || iframeDoc . querySelector < HTMLLinkElement > ( 'link[rel="apple-touch-icon"]' ) ;
64+
6065 if ( faviconLink ?. href ) {
6166 setFavicons ( ( prev ) => ( { ...prev , [ activeTab . id ] : faviconLink . href } ) ) ;
6267 } else if ( actualUrl ) {
6368 try {
6469 const urlObj = new URL ( actualUrl ) ;
6570 const defaultFavicon = `${ urlObj . origin } /favicon.ico` ;
6671 setFavicons ( ( prev ) => ( { ...prev , [ activeTab . id ] : defaultFavicon } ) ) ;
67- } catch ( e ) {
68- }
72+ } catch ( e ) { }
6973 }
7074 }
71- } catch ( e ) {
72- }
75+ } catch ( e ) { }
7376 } ;
7477
7578 iframe . addEventListener ( "load" , updateUrlBar ) ;
@@ -91,7 +94,7 @@ export default function Browser() {
9194 active : tab . id === id ,
9295 } ) ) ,
9396 ) ;
94-
97+
9598 const iframe = iframeRefs . current [ id ] ;
9699 const actualUrl = getActualUrl ( iframe ) ;
97100 setUrl ( actualUrl || target . url ) ;
@@ -156,7 +159,17 @@ export default function Browser() {
156159 } ;
157160
158161 const goHome = ( ) => {
159- window . location . href = '/' ;
162+ window . location . href = "/" ;
163+ } ;
164+
165+ const removeBookmark = ( index : number ) => {
166+ try {
167+ const updatedBookmarks = bookmarks . filter ( ( _ , i ) => i !== index ) ;
168+ setBookmarks ( updatedBookmarks ) ;
169+ localStorage . setItem ( "bookmarks" , JSON . stringify ( updatedBookmarks ) ) ;
170+ } catch ( e ) {
171+ console . error ( "Failed to remove bookmark:" , e ) ;
172+ }
160173 } ;
161174
162175 const goBack = ( ) => {
@@ -196,10 +209,22 @@ export default function Browser() {
196209
197210 if ( title && typeof localStorage !== "undefined" ) {
198211 try {
199- const bookmarks = JSON . parse ( localStorage . getItem ( "bookmarks" ) || "[]" ) ;
200- bookmarks . push ( { Title : title , url : actualUrl } ) ;
201- localStorage . setItem ( "bookmarks" , JSON . stringify ( bookmarks ) ) ;
202- console . log ( "Bookmark added:" , { Title : title , url : actualUrl } ) ;
212+ let faviconUrl = favicons [ activeTab . id ] || "" ;
213+
214+ if ( ! faviconUrl ) {
215+ try {
216+ const urlObj = new URL ( actualUrl ) ;
217+ faviconUrl = `${ urlObj . origin } /favicon.ico` ;
218+ } catch ( e ) {
219+ faviconUrl = "" ;
220+ }
221+ }
222+
223+ const newBookmark = { Title : title , url : actualUrl , favicon : faviconUrl } ;
224+ const updatedBookmarks = [ ...bookmarks , newBookmark ] ;
225+ setBookmarks ( updatedBookmarks ) ;
226+ localStorage . setItem ( "bookmarks" , JSON . stringify ( updatedBookmarks ) ) ;
227+ console . log ( "Bookmark added:" , newBookmark ) ;
203228 alert ( "Bookmark added successfully!" ) ;
204229 } catch ( e ) {
205230 console . error ( "Failed to add bookmark:" , e ) ;
@@ -227,10 +252,15 @@ export default function Browser() {
227252 >
228253 < div className = "flex min-w-0 flex-1 items-center gap-2" >
229254 { favicons [ tab . id ] ? (
230- < img src = { favicons [ tab . id ] } alt = "" className = "h-4 w-4 shrink-0 rounded" onError = { ( e ) => {
231- e . currentTarget . style . display = 'none' ;
232- e . currentTarget . nextElementSibling ?. classList . remove ( 'hidden' ) ;
233- } } />
255+ < img
256+ src = { favicons [ tab . id ] }
257+ alt = ""
258+ className = "h-4 w-4 shrink-0 rounded"
259+ onError = { ( e ) => {
260+ e . currentTarget . style . display = "none" ;
261+ e . currentTarget . nextElementSibling ?. classList . remove ( "hidden" ) ;
262+ } }
263+ />
234264 ) : null }
235265 < div className = { classNames ( "h-4 w-4 shrink-0 rounded bg-accent/30" , favicons [ tab . id ] ? "hidden" : "" ) } />
236266 < span className = "truncate text-sm" > { tab . title } </ span >
@@ -253,7 +283,7 @@ export default function Browser() {
253283 </ button >
254284 </ div >
255285
256- < div className = "flex items-center justify-between gap-3 border-b border-border/50 bg-background-secondary px-3 py-2 backdrop-blur-xl" >
286+ < div className = "flex items-center justify-between gap-3 bg-background-secondary px-3 py-2 backdrop-blur-xl" >
257287 < div className = "flex items-center gap-1" >
258288 < button type = "button" className = { iconButtonClass } onClick = { goHome } aria-label = "Home" >
259289 < Home className = "h-4 w-4" />
@@ -302,6 +332,42 @@ export default function Browser() {
302332 </ div >
303333 </ div >
304334
335+ { bookmarks . length > 0 && (
336+ < div className = "flex items-center gap-0.5 bg-background-secondary px-3 py-1.5 overflow-x-auto border-b border-border/50" >
337+ { bookmarks . map ( ( bookmark , index ) => (
338+ < button
339+ key = { index }
340+ type = "button"
341+ className = "inline-flex items-center gap-2 rounded-lg px-3 py-1.5 text-sm text-text-secondary hover:bg-interactive hover:scale-105 transition-all shrink-0"
342+ style = { { maxWidth : "195px" } }
343+ onClick = { ( ) => handleNavigate ( bookmark . url ) }
344+ onContextMenu = { ( e ) => {
345+ e . preventDefault ( ) ;
346+ if ( confirm ( `Remove bookmark "${ bookmark . Title } "?` ) ) {
347+ removeBookmark ( index ) ;
348+ }
349+ } }
350+ >
351+ { bookmark . favicon ? (
352+ < img
353+ src = { bookmark . favicon }
354+ alt = ""
355+ className = "h-[18px] w-[18px] shrink-0"
356+ onError = { ( e ) => {
357+ e . currentTarget . style . display = "none" ;
358+ e . currentTarget . nextElementSibling ?. classList . remove ( "hidden" ) ;
359+ } }
360+ />
361+ ) : null }
362+ < Star className = { `h-[18px] w-[18px] fill-current shrink-0 ${ bookmark . favicon ? "hidden" : "" } ` } />
363+ < span className = "overflow-hidden whitespace-nowrap block min-w-0" style = { { textOverflow : "ellipsis" } } >
364+ { bookmark . Title }
365+ </ span >
366+ </ button >
367+ ) ) }
368+ </ div >
369+ ) }
370+
305371 < div className = "relative flex-1 bg-background" >
306372 { tabs . map ( ( tab ) => (
307373 < iframe
@@ -318,4 +384,4 @@ export default function Browser() {
318384 </ div >
319385 </ div >
320386 ) ;
321- }
387+ }
0 commit comments