diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4af150e..88bc505 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Preserved underlying functionality for future re-implementation
### Fixed
+- Link and image rendering in social feeds
+ - Added URL detection and rendering in social post content
+ - Implemented image URL detection and automatic embedding in posts
+ - Created contentParser utility for parsing text content with links
+ - Enhanced EnhancedSocialPost component to properly render links and images
+ - Archived unused SocialPost component following the project's archiving pattern
+
- UI issues in workout completion flow
- Fixed weird line across input box in the 1301 event content box by adding subtle border styling
- Removed template options section that was causing bugs with workout templates
diff --git a/components/social/EnhancedSocialPost.tsx b/components/social/EnhancedSocialPost.tsx
index 91654c6..0559fe6 100644
--- a/components/social/EnhancedSocialPost.tsx
+++ b/components/social/EnhancedSocialPost.tsx
@@ -1,6 +1,6 @@
// components/social/EnhancedSocialPost.tsx
import React, { useEffect, useState, useMemo } from 'react';
-import { View, TouchableOpacity, Image, ScrollView } from 'react-native';
+import { View, TouchableOpacity, Image, ScrollView, Linking } from 'react-native';
import { Text } from '@/components/ui/text';
import { Badge } from '@/components/ui/badge';
import { Heart, MessageCircle, Repeat, Share, Clock, Dumbbell, CheckCircle, FileText, User } from 'lucide-react-native';
@@ -16,6 +16,7 @@ import {
ParsedWorkoutTemplate,
ParsedLongformContent
} from '@/types/nostr-workout';
+import { parseContent } from '@/utils/contentParser';
import { formatDistance } from 'date-fns';
import Markdown from 'react-native-markdown-display';
import { useExerciseNames, useTemplateExerciseNames } from '@/lib/hooks/useExerciseNames';
@@ -511,9 +512,46 @@ function TemplateContent({ template }: { template: ParsedWorkoutTemplate }) {
// Component for social posts
function SocialContent({ post }: { post: ParsedSocialPost }) {
- // Render the social post content
+ // Parse content to identify URLs and images
+ const contentSegments = useMemo(() => {
+ return parseContent(post.content);
+ }, [post.content]);
+
+ // Render the social post content with links and images
const renderMainContent = () => (
- {post.content}
+
+ {contentSegments.map((segment, index) => {
+ switch (segment.type) {
+ case 'text':
+ return {segment.content};
+
+ case 'image':
+ return (
+
+
+
+ );
+
+ case 'url':
+ return (
+ Linking.openURL(segment.content)}
+ activeOpacity={0.7}
+ >
+ {segment.content}
+
+ );
+
+ default:
+ return null;
+ }
+ })}
+
);
// Render quoted content if available
diff --git a/components/social/SocialPost.tsx b/components/social/archive/SocialPost.tsx
similarity index 96%
rename from components/social/SocialPost.tsx
rename to components/social/archive/SocialPost.tsx
index 7f3117f..9e830c6 100644
--- a/components/social/SocialPost.tsx
+++ b/components/social/archive/SocialPost.tsx
@@ -1,4 +1,9 @@
-// components/social/SocialPost.tsx
+// components/social/archive/SocialPost.tsx
+// ARCHIVED: April 9, 2025
+// This component was archived because it is no longer used in the application.
+// The app exclusively uses EnhancedSocialPost for rendering social feed content.
+// This file is kept for historical reference only.
+
import React from 'react';
import { View, TouchableOpacity } from 'react-native';
import { Text } from '@/components/ui/text';
@@ -234,4 +239,4 @@ export default function SocialPost({ post }: SocialPostProps) {
);
-}
\ No newline at end of file
+}
diff --git a/utils/contentParser.ts b/utils/contentParser.ts
new file mode 100644
index 0000000..21aade8
--- /dev/null
+++ b/utils/contentParser.ts
@@ -0,0 +1,90 @@
+// utils/contentParser.ts
+/**
+ * Utility functions for parsing content in social posts, including URL detection,
+ * image URL detection, and content segmentation for rendering.
+ */
+
+/**
+ * Regular expression for detecting URLs
+ * Matches common URL patterns with or without protocol
+ */
+const URL_REGEX = /(https?:\/\/[^\s]+)|(www\.[^\s]+)/gi;
+
+/**
+ * Regular expression for detecting image URLs
+ * Matches URLs ending with common image extensions
+ */
+const IMAGE_URL_REGEX = /\.(gif|jpe?g|tiff?|png|webp|bmp)(\?.*)?$/i;
+
+/**
+ * Interface for content segments
+ */
+export interface ContentSegment {
+ type: 'text' | 'url' | 'image';
+ content: string;
+}
+
+/**
+ * Check if a URL is an image URL based on its extension
+ *
+ * @param url URL to check
+ * @returns Boolean indicating if URL is an image
+ */
+export function isImageUrl(url: string): boolean {
+ return IMAGE_URL_REGEX.test(url);
+}
+
+/**
+ * Extract URLs from text
+ *
+ * @param text Text to extract URLs from
+ * @returns Array of URLs found in the text
+ */
+export function extractUrls(text: string): string[] {
+ return text.match(URL_REGEX) || [];
+}
+
+/**
+ * Parse text content into segments for rendering
+ * Each segment is either plain text, a URL, or an image URL
+ *
+ * @param content Text content to parse
+ * @returns Array of content segments
+ */
+export function parseContent(content: string): ContentSegment[] {
+ if (!content) return [];
+
+ const segments: ContentSegment[] = [];
+ const urls = extractUrls(content);
+
+ // If no URLs, return whole content as text
+ if (urls.length === 0) {
+ return [{ type: 'text', content }];
+ }
+
+ // Split content by URLs and create segments for each part
+ let remainingContent = content;
+
+ urls.forEach(url => {
+ const parts = remainingContent.split(url);
+
+ // Add text before URL if exists
+ if (parts[0]) {
+ segments.push({ type: 'text', content: parts[0] });
+ }
+
+ // Add URL (as image or link)
+ const type = isImageUrl(url) ? 'image' : 'url';
+ segments.push({ type, content: url });
+
+ // Update remaining content
+ remainingContent = parts.slice(1).join(url);
+ });
+
+ // Add any remaining text
+ if (remainingContent) {
+ segments.push({ type: 'text', content: remainingContent });
+ }
+
+ return segments;
+}