+52
src/components/FaviconIcon.tsx
+52
src/components/FaviconIcon.tsx
···
1
+
import { useState } from "react";
2
+
import { Globe, Settings as SettingsIcon } from "lucide-react";
3
+
4
+
interface FaviconIconProps {
5
+
url: string;
6
+
alt: string;
7
+
className?: string;
8
+
}
9
+
10
+
export default function FaviconIcon({ url, alt, className }: FaviconIconProps) {
11
+
const [loaded, setLoaded] = useState(false);
12
+
const [error, setError] = useState(false);
13
+
14
+
if (error) {
15
+
return (
16
+
<Globe
17
+
className={
18
+
className || "w-4 h-4 text-neutral-400 dark:text-neutral-500"
19
+
}
20
+
/>
21
+
);
22
+
}
23
+
24
+
return (
25
+
<>
26
+
{/* Fallback/Placeholder */}
27
+
{!loaded && (
28
+
<Globe
29
+
className={
30
+
className || "w-4 h-4 text-neutral-400 dark:text-neutral-500"
31
+
}
32
+
/>
33
+
)}
34
+
35
+
{/* The actual image */}
36
+
<img
37
+
src={url}
38
+
alt={`${alt} favicon`}
39
+
className={className || "h-4 w-4"}
40
+
// Use inline style to show only when loaded, preventing a broken image icon flicker
41
+
style={{ display: loaded ? "block" : "none" }}
42
+
onLoad={() => {
43
+
setLoaded(true);
44
+
setError(false);
45
+
}}
46
+
onError={() => {
47
+
setError(true);
48
+
}}
49
+
/>
50
+
</>
51
+
);
52
+
}
+11
-2
src/components/HistoryTab.tsx
+11
-2
src/components/HistoryTab.tsx
···
1
1
import { Upload, Sparkles, ChevronRight, Database } from "lucide-react";
2
2
import { ATPROTO_APPS } from "../constants/atprotoApps";
3
3
import type { Upload as UploadType } from "../types";
4
+
import FaviconIcon from "../components/FaviconIcon";
4
5
import type { UserSettings } from "../types/settings";
5
6
6
7
interface HistoryTabProps {
···
157
158
href={destApp.link}
158
159
target="_blank"
159
160
rel="noopener noreferrer"
160
-
className="text-sm text-purple-750 dark:text-cyan-250 hover:underline leading-tight"
161
+
className="text-sm text-purple-750 dark:text-cyan-250 hover:underline leading-tight flex items-center space-x-1"
161
162
>
162
-
{destApp.action} on {destApp.icon} {destApp.name}
163
+
<span>{destApp.action} on</span>
164
+
165
+
<FaviconIcon
166
+
url={destApp.icon}
167
+
alt={destApp.name}
168
+
className="w-3 h-3 mb-0.5 flex-shrink-0"
169
+
/>
170
+
171
+
<span>{destApp.name}</span>
163
172
</a>
164
173
)}
165
174
<div className="flex items-center flex-wrap gap-2">
+1
-1
src/components/SetupWizard.tsx
+1
-1
src/components/SetupWizard.tsx
+9
-8
src/constants/atprotoApps.ts
+9
-8
src/constants/atprotoApps.ts
···
4
4
bluesky: {
5
5
id: "bluesky",
6
6
name: "Bluesky",
7
-
description: "The main ATmosphere social network",
7
+
description: "Social app built for better conversations.",
8
8
link: "https://bsky.app/",
9
-
icon: "🦋",
9
+
icon: "https://web-cdn.bsky.app/static/apple-touch-icon.png",
10
10
action: "Follow",
11
11
followLexicon: "app.bsky.graph.follow",
12
12
enabled: true,
···
14
14
tangled: {
15
15
id: "tangled",
16
16
name: "Tangled",
17
-
description: "Alternative following for developers & creators",
17
+
description: "Tightly-knit social coding!",
18
18
link: "https://tangled.org/",
19
-
icon: "🐑",
19
+
icon: "https://tangled.org/favicon.ico",
20
20
action: "Follow",
21
21
followLexicon: "sh.tangled.graph.follow",
22
22
enabled: true,
···
24
24
spark: {
25
25
id: "spark",
26
26
name: "Spark",
27
-
description: "Short-form video focused social",
27
+
description:
28
+
"Social that puts the user in control, dedicated to video and photo.",
28
29
link: "https://sprk.so/",
29
-
icon: "✨",
30
+
icon: "https://sprk.so/favicon.ico",
30
31
action: "Follow",
31
32
followLexicon: "so.sprk.graph.follow",
32
33
enabled: true,
···
35
36
id: "bsky list",
36
37
name: "Bluesky List",
37
38
description: "Organize into custom Bluesky lists",
38
-
link: "https://bsky.app/",
39
-
icon: "📃",
39
+
link: "https://bsky.app",
40
+
icon: "https://web-cdn.bsky.app/static/apple-touch-icon.png",
40
41
action: "Add to",
41
42
followLexicon: "app.bsky.graph.follow",
42
43
enabled: false, // Not yet implemented