Skip to content

Commit f322178

Browse files
committed
Tabs - Added Favicons, Fixed Search, Shows Proper URL When Switching Tab
1 parent 1083276 commit f322178

File tree

2 files changed

+75
-21
lines changed

2 files changed

+75
-21
lines changed

src/components/Browser.tsx

Lines changed: 65 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,32 @@ import { useEffect, useMemo, useRef, useState } from "react";
33
import { actionBarClass, addressInputClass, classNames, closeButtonClass, encodeProxyUrl, formatUrl, getActualUrl, getDefaultUrl, iconButtonClass, type Tab, tabButtonClass } from "@/lib/tabs";
44

55
export default function Browser() {
6-
const [tabs, setTabs] = useState<Tab[]>([{ id: 1, title: "New Tab", url: "about:blank", active: true, reloadKey: 0 }]);
6+
const [tabs, setTabs] = useState<Tab[]>([{ id: 1, title: "Tab 1", url: "about:blank", active: true, reloadKey: 0 }]);
77
const [url, setUrl] = useState("about:blank");
8+
const [favicons, setFavicons] = useState<{ [key: number]: string }>({});
89
const activeTab = useMemo(() => tabs.find((tab) => tab.active), [tabs]);
910
const iframeRefs = useRef<{ [key: number]: HTMLIFrameElement | null }>({});
1011

1112
useEffect(() => {
12-
const defaultUrl = getDefaultUrl();
13-
setTabs((prev) => prev.map((tab) => ({ ...tab, url: defaultUrl })));
14-
setUrl(defaultUrl);
13+
let firstTabUrl = getDefaultUrl();
14+
try {
15+
const goUrl = sessionStorage.getItem("goUrl");
16+
if (goUrl && goUrl.trim()) {
17+
firstTabUrl = goUrl;
18+
}
19+
} catch (error) {
20+
console.warn("Session storage access failed:", error);
21+
}
22+
23+
setTabs((prev) => prev.map((tab) => ({ ...tab, url: firstTabUrl })));
24+
setUrl(firstTabUrl);
1525
}, []);
1626

1727
useEffect(() => {
1828
if (activeTab) {
19-
setUrl(activeTab.url);
29+
const iframe = iframeRefs.current[activeTab.id];
30+
const actualUrl = getActualUrl(iframe);
31+
setUrl(actualUrl || activeTab.url);
2032
}
2133
}, [activeTab]);
2234

@@ -30,13 +42,33 @@ export default function Browser() {
3042
const actualUrl = getActualUrl(iframe);
3143
if (actualUrl && actualUrl !== url) {
3244
setUrl(actualUrl);
45+
}
3346

34-
try {
35-
const hostname = new URL(actualUrl).hostname;
36-
setTabs((prev) => prev.map((tab) => (tab.id === activeTab.id ? { ...tab, title: hostname || "New Tab" } : tab)));
37-
} catch (e) {
38-
// Invalid URL
47+
try {
48+
const iframeTitle = iframe.contentWindow?.document?.title;
49+
if (iframeTitle && iframeTitle !== activeTab.title) {
50+
setTabs((prev) => prev.map((tab) => (tab.id === activeTab.id ? { ...tab, title: iframeTitle } : tab)));
3951
}
52+
53+
const iframeDoc = iframe.contentWindow?.document;
54+
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+
60+
if (faviconLink?.href) {
61+
setFavicons((prev) => ({ ...prev, [activeTab.id]: faviconLink.href }));
62+
} else if (actualUrl) {
63+
try {
64+
const urlObj = new URL(actualUrl);
65+
const defaultFavicon = `${urlObj.origin}/favicon.ico`;
66+
setFavicons((prev) => ({ ...prev, [activeTab.id]: defaultFavicon }));
67+
} catch (e) {
68+
}
69+
}
70+
}
71+
} catch (e) {
4072
}
4173
};
4274

@@ -59,14 +91,17 @@ export default function Browser() {
5991
active: tab.id === id,
6092
})),
6193
);
62-
setUrl(target.url);
94+
95+
const iframe = iframeRefs.current[id];
96+
const actualUrl = getActualUrl(iframe);
97+
setUrl(actualUrl || target.url);
6398
};
6499

65100
const addNewTab = () => {
66101
setTabs((prev) => {
67102
const nextId = prev.length ? Math.max(...prev.map((tab) => tab.id)) + 1 : 1;
68103
const newTabs = prev.map((tab) => ({ ...tab, active: false }));
69-
return [...newTabs, { id: nextId, title: "New Tab", url: getDefaultUrl(), active: true, reloadKey: 0 }];
104+
return [...newTabs, { id: nextId, title: `Tab ${nextId}`, url: getDefaultUrl(), active: true, reloadKey: 0 }];
70105
});
71106
setUrl(getDefaultUrl());
72107
};
@@ -77,8 +112,16 @@ export default function Browser() {
77112
const filtered = prev.filter((tab) => tab.id !== id);
78113

79114
if (filtered.length === 0) {
80-
const defaultUrl = getDefaultUrl();
81-
return [{ id: Date.now(), title: "New Tab", url: defaultUrl, active: true, reloadKey: 0 }];
115+
let firstTabUrl = getDefaultUrl();
116+
try {
117+
const goUrl = sessionStorage.getItem("goUrl");
118+
if (goUrl && goUrl.trim()) {
119+
firstTabUrl = goUrl;
120+
}
121+
} catch (error) {
122+
console.warn("Session storage access failed:", error);
123+
}
124+
return [{ id: Date.now(), title: "Tab 1", url: firstTabUrl, active: true, reloadKey: 0 }];
82125
} else if (!filtered.some((tab) => tab.active)) {
83126
filtered[0] = { ...filtered[0], active: true };
84127
nextUrl = filtered[0].url;
@@ -104,7 +147,6 @@ export default function Browser() {
104147
? {
105148
...tab,
106149
url: formattedUrl,
107-
title: new URL(formattedUrl).hostname || "New Tab",
108150
reloadKey: tab.reloadKey + 1,
109151
}
110152
: tab,
@@ -184,7 +226,13 @@ export default function Browser() {
184226
className={classNames(tabButtonClass, tab.active ? "bg-background-secondary text-text shadow-sm" : "bg-background text-text-secondary hover:bg-interactive")}
185227
>
186228
<div className="flex min-w-0 flex-1 items-center gap-2">
187-
<div className="h-4 w-4 shrink-0 rounded bg-accent/30" />
229+
{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+
}} />
234+
) : null}
235+
<div className={classNames("h-4 w-4 shrink-0 rounded bg-accent/30", favicons[tab.id] ? "hidden" : "")} />
188236
<span className="truncate text-sm">{tab.title}</span>
189237
</div>
190238
<button
@@ -270,4 +318,4 @@ export default function Browser() {
270318
</div>
271319
</div>
272320
);
273-
}
321+
}

src/lib/tabs.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ declare global {
1818
}
1919
}
2020

21-
export const baseTabs: Tab[] = [{ id: 1, title: "New Tab", url: "about:blank", active: true, reloadKey: 0 }];
21+
export const baseTabs: Tab[] = [{ id: 1, title: "Tab 1", url: "about:blank", active: true, reloadKey: 0 }];
2222

2323
export const formatUrl = (value: string) => {
2424
if (!value.trim()) return "about:blank";
@@ -39,7 +39,7 @@ export const getDefaultUrl = () => {
3939
}
4040

4141
try {
42-
return sessionStorage.getItem("goUrl") || localStorage.getItem("engine") || "https://duckduckgo.com";
42+
return localStorage.getItem("engine") || "https://duckduckgo.com";
4343
} catch (error) {
4444
console.warn("Storage access failed:", error);
4545
return "https://duckduckgo.com";
@@ -76,7 +76,13 @@ export const getActualUrl = (iframe: HTMLIFrameElement | null): string => {
7676
if (!iframe?.contentWindow) return "";
7777

7878
try {
79-
return (iframe.contentWindow as any).__uv$location?.href || iframe.contentWindow.location.href;
79+
const proxyUrl = iframe.contentWindow.location.href;
80+
const config = window.__uv$config;
81+
if (config && proxyUrl.includes(config.prefix)) {
82+
const encoded = proxyUrl.substring(proxyUrl.indexOf(config.prefix) + config.prefix.length);
83+
return config.decodeUrl(encoded);
84+
}
85+
return proxyUrl;
8086
} catch {
8187
return "";
8288
}
@@ -115,4 +121,4 @@ export const addBookmark = (iframeRefs: { [key: number]: HTMLIFrameElement | nul
115121
console.error("Failed to add bookmark:", err);
116122
alert("Failed to add bookmark. Storage may be blocked.");
117123
}
118-
};
124+
};

0 commit comments

Comments
 (0)