'use client'; import { useState, useEffect } from 'react'; import Link from 'next/link'; import { useRouter } from 'next/navigation'; import { HeartIcon, RepeatIcon, MessageIcon, FlagIcon, TrashIcon } from '@/components/Icons'; import { Bot, MoreHorizontal, UserX, VolumeX, Globe } from 'lucide-react'; import { Post, LinkPreviewMediaItem } from '@/lib/types'; import { useAuth } from '@/lib/contexts/AuthContext'; import { useToast } from '@/lib/contexts/ToastContext'; import { VideoEmbed } from '@/components/VideoEmbed'; import BlurredImage from '@/components/BlurredImage'; import BlurredVideo from '@/components/BlurredVideo'; import { useFormattedHandle } from '@/lib/utils/handle'; import { useDomain } from '@/lib/contexts/ConfigContext'; import { signedAPI } from '@/lib/api/signed-fetch'; import type { LinkPreviewData } from '@/lib/media/linkPreview'; import { AvatarImage } from '@/components/AvatarImage'; // Component for link preview image that hides on error function LinkPreviewImage({ src, alt }: { src: string; alt: string }) { const [hasError, setHasError] = useState(false); if (hasError) return null; return (
, /gi, '\n\n');
decoded = decoded.replace(/<[^>]+>/g, '');
return decoded.trim();
};
const renderContent = (content: string, hidePreviewUrl?: string) => {
const decoded = decodeHtmlEntities(content);
const parts = decoded.split(/(https?:\/\/[^\s]+)/g);
return parts.map((part, index) => {
if (part.match(/^https?:\/\/[^\s]+$/)) {
// If this URL matches the link preview URL, hide it entirely
if (hidePreviewUrl && part.includes(hidePreviewUrl.replace(/^https?:\/\/(www\.)?/, '').split('/')[0])) {
return null;
}
// Extract just the domain (TLD)
try {
const url = new URL(part);
const domain = url.hostname.replace(/^www\./, '');
return (
e.stopPropagation()}
title={part}
>
{domain}
);
} catch {
// Fallback if URL parsing fails
return (
e.stopPropagation()}
>
{part}
);
}
}
// Handle newlines
if (part.includes('\n')) {
return part.split('\n').map((line, lineIndex, arr) => (
{line}
{lineIndex < arr.length - 1 &&
, etc.)
decoded = decoded.replace(/
/gi, '\n');
decoded = decoded.replace(/<\/p>\s*
}
));
}
return {part};
});
};
// Build a synthetic replyTo for swarm replies
const effectiveReplyTo = post.replyTo || (post.swarmReplyToId && post.swarmReplyToAuthor ? {
id: post.swarmReplyToId,
content: post.swarmReplyToContent || '',
createdAt: post.createdAt, // Use same time as approximation
likesCount: 0,
repostsCount: 0,
repliesCount: 0,
author: typeof post.swarmReplyToAuthor === 'string'
? JSON.parse(post.swarmReplyToAuthor)
: post.swarmReplyToAuthor,
isSwarm: true,
nodeDomain: (typeof post.swarmReplyToAuthor === 'string'
? JSON.parse(post.swarmReplyToAuthor)
: post.swarmReplyToAuthor)?.nodeDomain,
} as Post : null);
const replyToHandle = effectiveReplyTo?.author?.handle ? useFormattedHandle(effectiveReplyTo.author.handle, effectiveReplyTo.nodeDomain) : '';
const repostHandle = useFormattedHandle(post.author.handle, post.nodeDomain);
const hasOwnContent = decodeHtmlEntities(post.content).trim().length > 0;
const isRepostEvent = Boolean(post.repostOf);
const effectivePreview = {
url: hydratedPreview?.url || post.linkPreviewUrl || null,
title: hydratedPreview?.title || post.linkPreviewTitle || null,
description: hydratedPreview?.description || post.linkPreviewDescription || null,
image: hydratedPreview?.image || post.linkPreviewImage || null,
type: hydratedPreview?.type || post.linkPreviewType || null,
videoUrl: hydratedPreview?.videoUrl || post.linkPreviewVideoUrl || null,
media: hydratedPreview?.media || post.linkPreviewMedia || null,
};
const rawPreviewMedia = (() => {
const mediaJson = (post as Post & { linkPreviewMediaJson?: string | null }).linkPreviewMediaJson;
if (!mediaJson) {
return [];
}
try {
const parsed = JSON.parse(mediaJson);
return Array.isArray(parsed) ? parsed : [];
} catch {
return [];
}
})();
const previewMedia = (effectivePreview.media && effectivePreview.media.length > 0)
? effectivePreview.media
: rawPreviewMedia.length > 0
? rawPreviewMedia
: effectivePreview.image
? [{ url: effectivePreview.image }]
: [];
const previewImage = previewMedia[0]?.url || effectivePreview.image || null;
const isEmbeddedVideo = Boolean(effectivePreview.url && effectivePreview.url.match(EMBED_VIDEO_REGEX));
const isRichVideoPreview = effectivePreview.type === 'video' && Boolean(effectivePreview.videoUrl);
const isGalleryPreview = effectivePreview.type === 'gallery' && previewMedia.length > 1;
const renderLinkPreviewCard = (compact = false) => {
if (!effectivePreview.url || isEmbeddedVideo) {
return null;
}
return (
e.stopPropagation()}
>
{isRichVideoPreview && effectivePreview.videoUrl ? (