import React, { useState, useEffect } from 'react'; import { apiClient, serverUrl } from "@/lib/db"; import { Link, useNavigate } from "react-router-dom"; import { User as UserIcon, LayoutGrid, StretchHorizontal, FileText, Save, X, Edit3, MoreVertical, Trash2, ArrowUp, ArrowDown, Heart, MessageCircle, Maximize, ImageIcon, Youtube, Music, Wand2, Map, Brush, Mail, Archive } from 'lucide-react'; import { useOrganization } from "@/contexts/OrganizationContext"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "@/components/ui/dialog"; import { Code } from "lucide-react"; import { T, translate } from "@/i18n"; import MarkdownRenderer from "@/components/MarkdownRenderer"; import Comments from "@/components/Comments"; import MagicWizardButton from "@/components/MagicWizardButton"; import { InlineDropZone } from "@/components/InlineDropZone"; import { Loader2 } from 'lucide-react'; const VidstackPlayer = React.lazy(() => import('@/player/components/VidstackPlayerImpl').then(m => ({ default: m.VidstackPlayerImpl }))); import { PostRendererProps } from '../types'; import { isVideoType, normalizeMediaType } from "@/lib/mediaRegistry"; import { getVideoUrlWithResolution } from "../utils"; import ResponsiveImage from "@/components/ResponsiveImage"; import { generateOfflineZip } from "@/utils/zipGenerator"; import { toast } from "sonner"; // Lazy load ImageEditor const ImageEditor = React.lazy(() => import("@/components/ImageEditor").then(module => ({ default: module.ImageEditor }))); const CommentCountButton = ({ pictureId, isOpen, onClick }: { pictureId: string, isOpen: boolean, onClick: () => void }) => { const [count, setCount] = useState(null); useEffect(() => { const fetchCount = async () => { try { const data = await apiClient<{ comments: any[] }>(`/api/pictures/${pictureId}/comments`); setCount(data.comments?.length ?? null); } catch { setCount(null); } }; fetchCount(); }, [pictureId]); return ( ); }; export const ArticleRenderer: React.FC = (props) => { const { post, authorProfile, mediaItems, localMediaItems, mediaItem, isOwner, isEditMode, isLiked, likesCount, localPost, setLocalPost, setLocalMediaItems, onEditModeToggle, onEditPost, onViewModeChange, onExportMarkdown, onSaveChanges, onDeletePost, onDeletePicture, onLike, onUnlinkImage, onRemoveFromPost, onEditPicture, onGalleryPickerOpen, onYouTubeAdd, onTikTokAdd, onAIWizardOpen, onInlineUpload, onMoveItem, onMediaSelect, onExpand, onDownload } = props; const currentItems = isEditMode ? localMediaItems : mediaItems; const [openCommentIds, setOpenCommentIds] = useState>(new Set()); const [editingImageId, setEditingImageId] = useState(null); const [cacheBustKeys, setCacheBustKeys] = React.useState>({}); const [isEmbedDialogOpen, setIsEmbedDialogOpen] = useState(false); const [isEmailDialogOpen, setIsEmailDialogOpen] = useState(false); const [isZipping, setIsZipping] = useState(false); const navigate = useNavigate(); const { orgSlug } = useOrganization(); const baseUrl = serverUrl || window.location.origin; const embedUrl = `${baseUrl}/embed/${post?.id || mediaItem.id}`; const embedCode = ``; const [emailHtml, setEmailHtml] = useState(''); const [isGeneratingPdf, setIsGeneratingPdf] = useState(false); const loadEmailHtml = async () => { if (!post?.id) return; try { const res = await fetch(`${baseUrl}/api/render/email/${post.id}`); if (res.ok) { const html = await res.text(); setEmailHtml(html); } } catch (e) { console.error("Failed to load email html", e); } }; useEffect(() => { if (mediaItem && !isEditMode) { const element = document.getElementById(`media-item-${mediaItem.id}`); if (element) { // element.scrollIntoView({ behavior: 'smooth', block: 'center' }); } } }, [mediaItem?.id, isEditMode]); return (
{/* Header */}
{authorProfile?.avatar_url ? ( ) : ( )}
{authorProfile?.display_name || `User ${mediaItem.user_id.slice(0, 8)}`}
{new Date(post?.created_at || mediaItem.created_at).toLocaleDateString(undefined, { year: 'numeric', month: 'long', day: 'numeric' })}
{/* Actions */}
{/* Owner Actions */} {isOwner && ( <>
{isEditMode ? (
) : ( <> Edit Meta Wizard Delete Post )} )}
{/* Title & Description */} {isEditMode ? (
setLocalPost((prev: any) => ({ ...(prev || { description: '' }), title: e.target.value }))} className="text-4xl font-bold tracking-tight h-auto py-2 bg-background" placeholder="Post Title" />