mirror of
https://github.com/logos-messaging/OpChan.git
synced 2026-01-02 12:53:10 +00:00
feat: add link rendering
This commit is contained in:
parent
d84f34b50c
commit
bd1e3ff832
@ -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
|
||||
|
||||
@ -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 && (
|
||||
|
||||
@ -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 */}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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, {
|
||||
|
||||
43
src/components/ui/link-renderer.tsx
Normal file
43
src/components/ui/link-renderer.tsx
Normal 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;
|
||||
Loading…
x
Reference in New Issue
Block a user