feat: add link rendering

This commit is contained in:
Danish Arora 2025-09-10 15:04:24 +05:30
parent d84f34b50c
commit bd1e3ff832
No known key found for this signature in database
GPG Key ID: 1C6EF37CDAE1426E
6 changed files with 57 additions and 5 deletions

View File

@ -5,6 +5,7 @@ import { formatDistanceToNow } from 'date-fns';
import { Skeleton } from '@/components/ui/skeleton';
import { MessageSquareText, Newspaper } from 'lucide-react';
import { AuthorDisplay } from './ui/author-display';
import { LinkRenderer } from './ui/link-renderer';
interface FeedItemBase {
id: string;
@ -135,7 +136,7 @@ const ActivityFeed: React.FC = () => {
) : (
<div>
<div className="text-sm line-clamp-3 mb-1">
{item.content}
<LinkRenderer text={item.content} />
</div>
<div className="text-xs text-muted-foreground">
{item.voteCount} Reply to post

View File

@ -11,6 +11,7 @@ import {
import { Button } from '@/components/ui/button';
import { BookmarkButton } from '@/components/ui/bookmark-button';
import { AuthorDisplay } from '@/components/ui/author-display';
import { LinkRenderer } from '@/components/ui/link-renderer';
import { usePending, usePendingVote } from '@/hooks/usePending';
import {
Tooltip,
@ -132,7 +133,9 @@ const CommentCard: React.FC<CommentCardProps> = ({
/>
</div>
<p className="text-sm break-words mb-2">{comment.content}</p>
<p className="text-sm break-words mb-2">
<LinkRenderer text={comment.content} />
</p>
<div className="flex items-center gap-2">
{canModerate && !comment.moderated && (

View File

@ -13,6 +13,7 @@ import {
import { RelevanceIndicator } from '@/components/ui/relevance-indicator';
import { AuthorDisplay } from '@/components/ui/author-display';
import { BookmarkButton } from '@/components/ui/bookmark-button';
import { LinkRenderer } from '@/components/ui/link-renderer';
import { usePending, usePendingVote } from '@/hooks/usePending';
import { useToast } from '@/components/ui/use-toast';
@ -187,7 +188,7 @@ const PostCard: React.FC<PostCardProps> = ({ post, commentCount = 0 }) => {
{/* Post content preview */}
<p className="text-cyber-neutral text-sm leading-relaxed mb-3">
{contentPreview}
<LinkRenderer text={contentPreview} />
</p>
{/* Post actions */}

View File

@ -24,6 +24,7 @@ import { formatDistanceToNow } from 'date-fns';
import { RelevanceIndicator } from './ui/relevance-indicator';
import { AuthorDisplay } from './ui/author-display';
import { BookmarkButton } from './ui/bookmark-button';
import { LinkRenderer } from './ui/link-renderer';
import CommentCard from './CommentCard';
import { usePending, usePendingVote } from '@/hooks/usePending';
@ -236,7 +237,7 @@ const PostDetail = () => {
/>
</div>
<p className="text-sm whitespace-pre-wrap break-words">
{post.content}
<LinkRenderer text={post.content} />
</p>
</div>
</div>

View File

@ -13,6 +13,7 @@ import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea';
import { Skeleton } from '@/components/ui/skeleton';
import { LinkRenderer } from '@/components/ui/link-renderer';
import {
ArrowLeft,
MessageSquare,
@ -300,7 +301,9 @@ const PostList = () => {
<h2 className="text-lg font-bold hover:text-cyber-accent">
{post.title}
</h2>
<p className="line-clamp-2 text-sm mb-3">{post.content}</p>
<p className="line-clamp-2 text-sm mb-3">
<LinkRenderer text={post.content} />
</p>
<div className="flex items-center gap-4 text-xs text-cyber-neutral">
<span>
{formatDistanceToNow(post.timestamp, {

View File

@ -0,0 +1,43 @@
import React from 'react';
interface LinkRendererProps {
text: string;
className?: string;
}
/**
* Component that renders text with clickable links
* Detects URLs and converts them to clickable <a> tags
*/
export const LinkRenderer: React.FC<LinkRendererProps> = ({ text, className }) => {
// URL regex pattern that matches http/https URLs
const urlRegex = /(https?:\/\/[^\s]+)/g;
// Split text by URLs and create array of text segments and URLs
const parts = text.split(urlRegex);
return (
<span className={className}>
{parts.map((part, index) => {
// Check if this part is a URL
if (urlRegex.test(part)) {
return (
<a
key={index}
href={part}
target="_blank"
rel="noopener noreferrer"
className="text-cyber-accent hover:text-cyber-accent/80 underline transition-colors"
>
{part}
</a>
);
}
// Regular text
return part;
})}
</span>
);
};
export default LinkRenderer;