Правки профиля, работа с ссылками
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
interface LinkPreviewProps {
|
||||
url: string;
|
||||
}
|
||||
|
||||
interface MicrolinkData {
|
||||
publisher?: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
image?: { url: string };
|
||||
logo?: { url: string };
|
||||
}
|
||||
|
||||
export default function LinkPreview({ url }: LinkPreviewProps) {
|
||||
const [data, setData] = useState<MicrolinkData | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
let isMounted = true;
|
||||
setLoading(true);
|
||||
|
||||
// Check cache first to avoid rate limiting
|
||||
const cacheKey = `link_preview_${url}`;
|
||||
const cached = sessionStorage.getItem(cacheKey);
|
||||
if (cached) {
|
||||
try {
|
||||
const parsed = JSON.parse(cached);
|
||||
if (isMounted) {
|
||||
setData(parsed);
|
||||
setLoading(false);
|
||||
}
|
||||
return;
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
fetch(`https://api.microlink.io?url=${encodeURIComponent(url)}`)
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
if (isMounted && res.status === 'success' && res.data) {
|
||||
setData(res.data);
|
||||
sessionStorage.setItem(cacheKey, JSON.stringify(res.data));
|
||||
}
|
||||
if (isMounted) setLoading(false);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Failed to fetch link preview:', err);
|
||||
if (isMounted) setLoading(false);
|
||||
});
|
||||
|
||||
return () => {
|
||||
isMounted = false;
|
||||
};
|
||||
}, [url]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="mt-2 text-xs text-vortex-400 opacity-70 italic border-l-[3px] border-vortex-500/50 pl-2">
|
||||
Загрузка предпросмотра...
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!data || (!data.title && !data.description && !data.image)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Domain for publisher fallback
|
||||
let domain = data.publisher;
|
||||
if (!domain) {
|
||||
try {
|
||||
domain = new URL(url).hostname.replace(/^www\./, '');
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
return (
|
||||
<a
|
||||
href={url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="block mt-2 border-l-[3px] border-vortex-500 bg-black/20 rounded-r-lg overflow-hidden hover:bg-black/30 transition-colors"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="p-2.5 flex flex-col gap-1.5">
|
||||
<div className="flex items-center gap-1.5 text-xs font-semibold text-vortex-400">
|
||||
{data.logo?.url && <img src={data.logo.url} alt="" className="w-3.5 h-3.5 rounded-sm object-cover" />}
|
||||
<span className="truncate">{domain}</span>
|
||||
</div>
|
||||
{data.title && <div className="text-sm font-bold text-white leading-tight break-words">{data.title}</div>}
|
||||
{data.description && <div className="text-[13px] text-zinc-300 line-clamp-3 leading-snug">{data.description}</div>}
|
||||
</div>
|
||||
{data.image?.url && (
|
||||
<div className="w-full relative overflow-hidden bg-black/20" style={{ maxHeight: '300px' }}>
|
||||
<img src={data.image.url} alt="" className="w-full h-full object-cover" />
|
||||
</div>
|
||||
)}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user