Skip to content

Commit aa8d000

Browse files
committed
Added ExternLink component
1 parent 7519d04 commit aa8d000

File tree

1 file changed

+107
-0
lines changed

1 file changed

+107
-0
lines changed

components/ExternLink.tsx

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import React, { useEffect, useState } from 'react';
2+
import {IconWebhook, IconChevronRight, Icon} from '@tabler/icons-react'
3+
4+
interface ExternLinkProps {
5+
href: string;
6+
icon?: React.ReactNode | React.ElementType;
7+
manualTitle?: string; // Neuer Prop für manuellen Titel
8+
}
9+
10+
interface LinkMetaData {
11+
title: string;
12+
description: string;
13+
icon: string;
14+
}
15+
16+
const fetchMetaData = async (url: string): Promise<LinkMetaData> => {
17+
try {
18+
const response = await fetch(url);
19+
if (!response.ok) {
20+
throw new Error('Network response was not ok');
21+
}
22+
const text = await response.text(); // Hole den HTML-Inhalt
23+
const parser = new DOMParser();
24+
const doc = parser.parseFromString(text, 'text/html');
25+
26+
const title = doc.querySelector('meta[property="og:title"]')?.getAttribute('content') ||
27+
doc.querySelector('title')?.textContent ||
28+
'No title';
29+
const description = doc.querySelector('meta[property="og:description"]')?.getAttribute('content') ||
30+
doc.querySelector('meta[name="description"]')?.getAttribute('content') ||
31+
'No description';
32+
const icon = doc.querySelector('link[rel="icon"]')?.getAttribute('href') ||
33+
doc.querySelector('link[rel="shortcut icon"]')?.getAttribute('href') ||
34+
'https://example.com/default-icon.png';
35+
36+
return { title, description, icon: new URL(icon, url).href };
37+
} catch (error) {
38+
console.error('Error fetching metadata:', error);
39+
return {
40+
title: 'Error',
41+
description: 'Unable to fetch metadata.',
42+
icon: 'https://example.com/error-icon.png',
43+
};
44+
}
45+
};
46+
47+
const ExternLink: React.FC<ExternLinkProps> = ({ href, icon, manualTitle }) => {
48+
const [metaData, setMetaData] = useState<LinkMetaData | null>(null);
49+
const [loading, setLoading] = useState(true);
50+
51+
useEffect(() => {
52+
if (!manualTitle) {
53+
fetchMetaData(href).then((data) => {
54+
setMetaData(data);
55+
setLoading(false);
56+
});
57+
} else {
58+
setMetaData({
59+
title: manualTitle,
60+
description: 'No description available',
61+
icon: 'https://example.com/web-icon.png', // Web-Icon für manuellen Titel
62+
});
63+
setLoading(false);
64+
}
65+
}, [href, manualTitle]);
66+
67+
if (loading) {
68+
return <div>Loading...</div>; // Display a loading state while fetching metadata
69+
}
70+
71+
return (
72+
<a
73+
href={href}
74+
className="w-full border border-gray-200 shadow-sm hover:shadow-md dark:border-neutral-700 dark:hover:border-neutral-600 transition-all duration-200 dark:bg-neutral-900 bg-white rounded-lg overflow-hidden flex flex-col justify-start relative my-4"
75+
target="_blank"
76+
rel="noopener noreferrer"
77+
>
78+
<div className="flex items-center p-4">
79+
{/* Icon */}
80+
{icon ? (
81+
typeof icon === 'string' ? (
82+
<img src={icon} alt="Favicon" className="w-6 h-6 mr-3" />
83+
) : (
84+
React.createElement(icon, { className: 'w-6 h-6 mr-3' })
85+
)
86+
) : (
87+
<img src={metaData?.icon} alt="Favicon" className="w-6 h-6 mr-3" />
88+
)}
89+
{/* Title */}
90+
<div className="flex flex-col">
91+
<span className="font-semibold text-lg text-gray-700 dark:text-gray-100">
92+
{metaData?.title}
93+
</span>
94+
<span className="text-sm text-gray-500 dark:text-gray-300">
95+
{metaData?.description}
96+
</span>
97+
</div>
98+
{/* Arrow Icon */}
99+
<span className="absolute right-4 top-1/2 transform -translate-y-1/2 text-gray-600 dark:text-gray-300">
100+
<IconChevronRight />
101+
</span>
102+
</div>
103+
</a>
104+
);
105+
};
106+
107+
export default ExternLink;

0 commit comments

Comments
 (0)