2025-11-03 20:35:01 +00:00
|
|
|
|
import { useState } from 'react'
|
|
|
|
|
|
import { useNavigate } from 'react-router-dom'
|
2025-12-04 20:27:45 +00:00
|
|
|
|
import { createPortal } from 'react-dom'
|
2025-12-04 17:44:05 +00:00
|
|
|
|
import { Heart, MessageCircle, MoreVertical, ChevronLeft, ChevronRight, Download, Send, X, ZoomIn, Share2 } from 'lucide-react'
|
2025-11-04 21:51:05 +00:00
|
|
|
|
import { likePost, deletePost, sendPhotoToTelegram } from '../utils/api'
|
2025-12-04 17:44:05 +00:00
|
|
|
|
import { hapticFeedback, showConfirm, openTelegramLink } from '../utils/telegram'
|
2025-12-04 20:00:39 +00:00
|
|
|
|
import { decodeHtmlEntities } from '../utils/htmlEntities'
|
|
|
|
|
|
import CommentsModal from './CommentsModal'
|
|
|
|
|
|
import PostMenu from './PostMenu'
|
2025-12-15 08:04:16 +00:00
|
|
|
|
import MusicAttachment from './MusicAttachment'
|
2025-11-03 20:35:01 +00:00
|
|
|
|
import './PostCard.css'
|
|
|
|
|
|
|
|
|
|
|
|
const TAG_COLORS = {
|
|
|
|
|
|
furry: '#FF8A33',
|
|
|
|
|
|
anime: '#4A90E2',
|
|
|
|
|
|
other: '#A0A0A0'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const TAG_NAMES = {
|
|
|
|
|
|
furry: 'Furry',
|
|
|
|
|
|
anime: 'Anime',
|
|
|
|
|
|
other: 'Other'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-01 17:57:05 +00:00
|
|
|
|
export default function PostCard({ post, currentUser, onUpdate, onAuthRequired }) {
|
2025-11-03 20:35:01 +00:00
|
|
|
|
const navigate = useNavigate()
|
2025-12-01 05:40:27 +00:00
|
|
|
|
const [liked, setLiked] = useState(post.likes?.includes(currentUser.id) || false)
|
|
|
|
|
|
const [likesCount, setLikesCount] = useState(post.likes?.length || 0)
|
2025-11-03 22:17:25 +00:00
|
|
|
|
const [currentImageIndex, setCurrentImageIndex] = useState(0)
|
2025-11-21 01:14:56 +00:00
|
|
|
|
const [showFullView, setShowFullView] = useState(false)
|
2025-12-04 20:00:39 +00:00
|
|
|
|
const [showComments, setShowComments] = useState(false)
|
|
|
|
|
|
const [showMenu, setShowMenu] = useState(false)
|
2025-12-04 22:28:51 +00:00
|
|
|
|
const [menuButtonPosition, setMenuButtonPosition] = useState(null)
|
2025-11-03 22:17:25 +00:00
|
|
|
|
|
2026-01-01 17:57:05 +00:00
|
|
|
|
const isGuest = currentUser?.isGuest === true
|
|
|
|
|
|
|
2025-12-01 05:40:27 +00:00
|
|
|
|
// Проверка на существование автора
|
|
|
|
|
|
if (!post.author) {
|
|
|
|
|
|
console.warn('[PostCard] Post without author:', post._id)
|
|
|
|
|
|
return null // Не показываем посты без автора
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-03 22:17:25 +00:00
|
|
|
|
// Поддержка и старого поля imageUrl и нового images
|
2025-12-01 05:40:27 +00:00
|
|
|
|
// Фильтруем старые URL из Telegram API
|
|
|
|
|
|
const allImages = post.images && post.images.length > 0 ? post.images : (post.imageUrl ? [post.imageUrl] : [])
|
|
|
|
|
|
const images = allImages.filter(img => {
|
|
|
|
|
|
// Игнорируем старые URL из Telegram API
|
|
|
|
|
|
if (img && typeof img === 'string' && img.includes('api.telegram.org/file/bot')) {
|
|
|
|
|
|
console.warn('[PostCard] Skipping old Telegram URL:', img)
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
return true
|
|
|
|
|
|
})
|
2025-11-03 20:35:01 +00:00
|
|
|
|
|
|
|
|
|
|
const handleLike = async () => {
|
2026-01-01 17:57:05 +00:00
|
|
|
|
// Проверка: гость не может лайкать
|
|
|
|
|
|
if (isGuest) {
|
|
|
|
|
|
if (onAuthRequired) {
|
|
|
|
|
|
onAuthRequired('Войдите, чтобы лайкать посты')
|
|
|
|
|
|
}
|
|
|
|
|
|
hapticFeedback('error')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-03 20:35:01 +00:00
|
|
|
|
try {
|
|
|
|
|
|
hapticFeedback('light')
|
|
|
|
|
|
const result = await likePost(post._id)
|
|
|
|
|
|
setLiked(result.liked)
|
|
|
|
|
|
setLikesCount(result.likes)
|
|
|
|
|
|
if (result.liked) {
|
|
|
|
|
|
hapticFeedback('success')
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Ошибка лайка:', error)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const handleDelete = async () => {
|
|
|
|
|
|
const confirmed = await showConfirm('Удалить этот пост?')
|
|
|
|
|
|
if (confirmed) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
await deletePost(post._id)
|
|
|
|
|
|
hapticFeedback('success')
|
|
|
|
|
|
onUpdate()
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Ошибка удаления:', error)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const formatDate = (date) => {
|
|
|
|
|
|
const d = new Date(date)
|
|
|
|
|
|
return d.toLocaleDateString('ru-RU', { day: 'numeric', month: 'short' })
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const goToProfile = () => {
|
2025-12-01 05:40:27 +00:00
|
|
|
|
if (post.author?._id) {
|
|
|
|
|
|
navigate(`/user/${post.author._id}`)
|
|
|
|
|
|
}
|
2025-11-03 20:35:01 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-21 01:14:56 +00:00
|
|
|
|
const openFullView = () => {
|
|
|
|
|
|
if (images.length > 0) {
|
|
|
|
|
|
setShowFullView(true)
|
|
|
|
|
|
hapticFeedback('light')
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const handleNext = () => {
|
|
|
|
|
|
if (currentImageIndex < images.length - 1) {
|
|
|
|
|
|
setCurrentImageIndex(currentImageIndex + 1)
|
|
|
|
|
|
hapticFeedback('light')
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const handlePrev = () => {
|
|
|
|
|
|
if (currentImageIndex > 0) {
|
|
|
|
|
|
setCurrentImageIndex(currentImageIndex - 1)
|
|
|
|
|
|
hapticFeedback('light')
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const handleDownloadImage = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
hapticFeedback('light')
|
|
|
|
|
|
const imageUrl = images[currentImageIndex]
|
|
|
|
|
|
await sendPhotoToTelegram(imageUrl)
|
|
|
|
|
|
hapticFeedback('success')
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Ошибка отправки фото:', error)
|
|
|
|
|
|
hapticFeedback('error')
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-04 17:44:05 +00:00
|
|
|
|
const handleRepost = () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
hapticFeedback('light')
|
|
|
|
|
|
|
|
|
|
|
|
// Получить имя бота из переменных окружения или использовать дефолтное
|
|
|
|
|
|
const botName = import.meta.env.VITE_TELEGRAM_BOT_NAME || 'NakamaSpaceBot'
|
|
|
|
|
|
|
|
|
|
|
|
// Создать deeplink для открытия поста в миниапп
|
2025-12-04 18:02:36 +00:00
|
|
|
|
// Используем startapp для миниаппов - это правильный формат для передачи параметра в миниапп
|
2025-12-04 17:44:05 +00:00
|
|
|
|
const deeplink = `https://t.me/${botName}?startapp=post_${post._id}`
|
|
|
|
|
|
|
|
|
|
|
|
// Открыть нативное окно "Поделиться" в Telegram
|
|
|
|
|
|
const shareUrl = `https://t.me/share/url?url=${encodeURIComponent(deeplink)}&text=${encodeURIComponent('Смотри пост в Nakama!')}`
|
|
|
|
|
|
|
|
|
|
|
|
openTelegramLink(shareUrl)
|
|
|
|
|
|
hapticFeedback('success')
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Ошибка репоста:', error)
|
|
|
|
|
|
hapticFeedback('error')
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-03 20:35:01 +00:00
|
|
|
|
return (
|
|
|
|
|
|
<div className="post-card card fade-in">
|
|
|
|
|
|
{/* Хедер поста */}
|
|
|
|
|
|
<div className="post-header">
|
|
|
|
|
|
<div className="post-author" onClick={goToProfile}>
|
|
|
|
|
|
<img
|
2025-12-01 05:40:27 +00:00
|
|
|
|
src={post.author?.photoUrl || '/default-avatar.png'}
|
|
|
|
|
|
alt={post.author?.username || post.author?.firstName || 'User'}
|
2025-11-03 20:35:01 +00:00
|
|
|
|
className="author-avatar"
|
2025-12-01 05:40:27 +00:00
|
|
|
|
onError={(e) => {
|
|
|
|
|
|
e.target.src = '/default-avatar.png'
|
|
|
|
|
|
}}
|
2025-11-03 20:35:01 +00:00
|
|
|
|
/>
|
|
|
|
|
|
<div className="author-info">
|
|
|
|
|
|
<div className="author-name">
|
2025-12-01 05:40:27 +00:00
|
|
|
|
{post.author?.firstName || ''} {post.author?.lastName || ''}
|
|
|
|
|
|
{!post.author?.firstName && !post.author?.lastName && 'Пользователь'}
|
2025-11-03 20:35:01 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
<div className="post-date">
|
2025-12-01 05:40:27 +00:00
|
|
|
|
@{post.author?.username || post.author?.firstName || 'user'} · {formatDate(post.createdAt)}
|
2025-11-03 20:35:01 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-11-04 21:51:05 +00:00
|
|
|
|
<button
|
|
|
|
|
|
className="menu-btn"
|
2025-12-04 22:28:51 +00:00
|
|
|
|
data-menu-button={post._id}
|
2025-11-04 21:51:05 +00:00
|
|
|
|
onClick={(e) => {
|
|
|
|
|
|
e.stopPropagation()
|
2025-12-04 22:28:51 +00:00
|
|
|
|
const button = e.currentTarget
|
|
|
|
|
|
const rect = button.getBoundingClientRect()
|
|
|
|
|
|
setMenuButtonPosition({
|
|
|
|
|
|
top: rect.top,
|
|
|
|
|
|
left: rect.left,
|
|
|
|
|
|
bottom: rect.bottom,
|
|
|
|
|
|
right: rect.right
|
|
|
|
|
|
})
|
2025-12-04 20:00:39 +00:00
|
|
|
|
setShowMenu(true)
|
2025-11-04 21:51:05 +00:00
|
|
|
|
}}
|
|
|
|
|
|
>
|
2025-11-03 20:35:01 +00:00
|
|
|
|
<MoreVertical size={20} />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Контент */}
|
|
|
|
|
|
{post.content && (
|
|
|
|
|
|
<div className="post-content">
|
2025-12-04 20:00:39 +00:00
|
|
|
|
{decodeHtmlEntities(post.content)}
|
2025-11-03 20:35:01 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
2025-11-03 22:17:25 +00:00
|
|
|
|
{/* Изображения */}
|
|
|
|
|
|
{images.length > 0 && (
|
|
|
|
|
|
<div className="post-images">
|
2025-12-08 15:43:52 +00:00
|
|
|
|
<div className="image-carousel" style={{ cursor: 'pointer', position: 'relative' }} onClick={openFullView}>
|
2025-11-03 22:17:25 +00:00
|
|
|
|
<img src={images[currentImageIndex]} alt={`Image ${currentImageIndex + 1}`} />
|
|
|
|
|
|
|
2025-12-08 15:43:52 +00:00
|
|
|
|
{images.length > 1 && (
|
2025-12-08 15:36:51 +00:00
|
|
|
|
<>
|
|
|
|
|
|
{/* Левая зона для переключения на предыдущее изображение */}
|
|
|
|
|
|
<div
|
|
|
|
|
|
className="carousel-zone carousel-zone-left"
|
|
|
|
|
|
onClick={(e) => {
|
|
|
|
|
|
e.stopPropagation()
|
|
|
|
|
|
if (currentImageIndex > 0) {
|
|
|
|
|
|
handlePrev()
|
2025-12-08 15:43:52 +00:00
|
|
|
|
} else {
|
|
|
|
|
|
openFullView()
|
2025-12-08 15:36:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
}}
|
2025-12-08 15:43:52 +00:00
|
|
|
|
style={{ cursor: 'pointer' }}
|
2025-12-08 15:36:51 +00:00
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Правая зона для переключения на следующее изображение */}
|
|
|
|
|
|
<div
|
|
|
|
|
|
className="carousel-zone carousel-zone-right"
|
|
|
|
|
|
onClick={(e) => {
|
|
|
|
|
|
e.stopPropagation()
|
|
|
|
|
|
if (currentImageIndex < images.length - 1) {
|
|
|
|
|
|
handleNext()
|
2025-12-08 15:43:52 +00:00
|
|
|
|
} else {
|
|
|
|
|
|
openFullView()
|
2025-12-08 15:36:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
}}
|
2025-12-08 15:43:52 +00:00
|
|
|
|
style={{ cursor: 'pointer' }}
|
2025-12-08 15:36:51 +00:00
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="carousel-dots">
|
|
|
|
|
|
{images.map((_, index) => (
|
|
|
|
|
|
<span
|
|
|
|
|
|
key={index}
|
|
|
|
|
|
className={`dot ${index === currentImageIndex ? 'active' : ''}`}
|
|
|
|
|
|
onClick={(e) => { e.stopPropagation(); setCurrentImageIndex(index); }}
|
|
|
|
|
|
/>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</>
|
2025-11-03 22:17:25 +00:00
|
|
|
|
)}
|
2025-11-21 01:14:56 +00:00
|
|
|
|
|
|
|
|
|
|
{/* Индикатор что можно открыть fullview */}
|
2025-12-08 15:36:51 +00:00
|
|
|
|
<div className="fullview-hint" onClick={(e) => { e.stopPropagation(); openFullView(); }}>
|
2025-11-21 01:14:56 +00:00
|
|
|
|
<ZoomIn size={20} />
|
|
|
|
|
|
</div>
|
2025-11-03 22:17:25 +00:00
|
|
|
|
</div>
|
2025-11-03 20:35:01 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
2025-12-15 20:37:41 +00:00
|
|
|
|
{/* Музыкальный трек (после изображений, компактно) */}
|
|
|
|
|
|
{post.attachedTrack && (
|
|
|
|
|
|
<div className="post-music">
|
|
|
|
|
|
<MusicAttachment track={post.attachedTrack} />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
2025-11-03 20:35:01 +00:00
|
|
|
|
{/* Теги */}
|
|
|
|
|
|
<div className="post-tags">
|
2025-12-08 13:55:11 +00:00
|
|
|
|
{post.tags.map((tag, index) => {
|
|
|
|
|
|
// Для известных тегов используем цвета и имена из объектов
|
|
|
|
|
|
// Для неизвестных тегов используем дефолтный цвет и само имя тега
|
|
|
|
|
|
const tagColor = TAG_COLORS[tag] || '#A0A0A0' // Дефолтный серый цвет
|
|
|
|
|
|
// Если нет имени в TAG_NAMES, используем сам тег с заглавной первой буквой
|
|
|
|
|
|
const tagName = TAG_NAMES[tag] || (tag.charAt(0).toUpperCase() + tag.slice(1))
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<span
|
|
|
|
|
|
key={index}
|
|
|
|
|
|
className="post-tag"
|
|
|
|
|
|
style={{ backgroundColor: tagColor }}
|
|
|
|
|
|
>
|
|
|
|
|
|
{tagName}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
)
|
|
|
|
|
|
})}
|
2025-11-03 20:35:01 +00:00
|
|
|
|
{post.isNSFW && (
|
|
|
|
|
|
<span className="nsfw-badge">NSFW</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Действия */}
|
|
|
|
|
|
<div className="post-actions">
|
|
|
|
|
|
<button
|
|
|
|
|
|
className={`action-btn ${liked ? 'active' : ''}`}
|
2025-11-04 21:51:05 +00:00
|
|
|
|
onClick={(e) => {
|
|
|
|
|
|
e.stopPropagation()
|
|
|
|
|
|
handleLike()
|
|
|
|
|
|
}}
|
2025-11-03 20:35:01 +00:00
|
|
|
|
>
|
2025-11-03 20:54:59 +00:00
|
|
|
|
<Heart size={20} fill={liked ? '#FF3B30' : 'none'} stroke={liked ? '#FF3B30' : 'currentColor'} />
|
2025-11-03 20:35:01 +00:00
|
|
|
|
<span>{likesCount}</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
|
2025-11-04 21:51:05 +00:00
|
|
|
|
<button
|
|
|
|
|
|
className="action-btn"
|
|
|
|
|
|
onClick={(e) => {
|
|
|
|
|
|
e.stopPropagation()
|
2025-12-04 20:00:39 +00:00
|
|
|
|
setShowComments(true)
|
2025-11-04 21:51:05 +00:00
|
|
|
|
}}
|
|
|
|
|
|
>
|
2025-11-03 20:54:59 +00:00
|
|
|
|
<MessageCircle size={20} stroke="currentColor" />
|
2025-11-03 20:35:01 +00:00
|
|
|
|
<span>{post.comments.length}</span>
|
|
|
|
|
|
</button>
|
2025-11-04 21:51:05 +00:00
|
|
|
|
|
2025-12-04 17:44:05 +00:00
|
|
|
|
<button
|
|
|
|
|
|
className="action-btn"
|
|
|
|
|
|
onClick={(e) => {
|
|
|
|
|
|
e.stopPropagation()
|
|
|
|
|
|
handleRepost()
|
|
|
|
|
|
}}
|
|
|
|
|
|
title="Поделиться постом"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Share2 size={20} stroke="currentColor" />
|
|
|
|
|
|
</button>
|
2025-11-03 20:35:01 +00:00
|
|
|
|
</div>
|
2025-11-21 01:14:56 +00:00
|
|
|
|
|
2025-12-04 20:27:45 +00:00
|
|
|
|
{/* Fullview модал через Portal */}
|
|
|
|
|
|
{showFullView && createPortal(
|
2025-12-04 20:11:28 +00:00
|
|
|
|
<div
|
|
|
|
|
|
className="image-fullview"
|
|
|
|
|
|
onMouseDown={(e) => e.stopPropagation()}
|
|
|
|
|
|
onTouchStart={(e) => e.stopPropagation()}
|
|
|
|
|
|
onClick={(e) => {
|
|
|
|
|
|
e.stopPropagation()
|
|
|
|
|
|
e.preventDefault()
|
|
|
|
|
|
setShowFullView(false)
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
<div className="fullview-header" onClick={(e) => e.stopPropagation()}>
|
2025-11-21 01:14:56 +00:00
|
|
|
|
<button className="fullview-btn" onClick={(e) => { e.stopPropagation(); setShowFullView(false); }}>
|
|
|
|
|
|
<X size={24} />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<span className="fullview-counter">
|
|
|
|
|
|
{currentImageIndex + 1} / {images.length}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<button
|
|
|
|
|
|
className="fullview-btn"
|
|
|
|
|
|
onClick={(e) => { e.stopPropagation(); handleDownloadImage(); }}
|
|
|
|
|
|
title="Отправить в Telegram"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Download size={24} />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="fullview-content" onClick={(e) => e.stopPropagation()}>
|
|
|
|
|
|
<img
|
|
|
|
|
|
src={images[currentImageIndex]}
|
|
|
|
|
|
alt={`Full view ${currentImageIndex + 1}`}
|
|
|
|
|
|
draggable={false}
|
2025-12-04 20:11:28 +00:00
|
|
|
|
onClick={(e) => e.stopPropagation()}
|
2025-11-21 01:14:56 +00:00
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
|
|
{images.length > 1 && (
|
|
|
|
|
|
<>
|
2025-12-08 15:36:51 +00:00
|
|
|
|
{/* Левая зона для переключения на предыдущее изображение */}
|
|
|
|
|
|
<div
|
|
|
|
|
|
className="fullview-zone fullview-zone-left"
|
|
|
|
|
|
onClick={(e) => {
|
|
|
|
|
|
e.stopPropagation()
|
|
|
|
|
|
if (currentImageIndex > 0) {
|
|
|
|
|
|
handlePrev()
|
|
|
|
|
|
}
|
|
|
|
|
|
}}
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Правая зона для переключения на следующее изображение */}
|
|
|
|
|
|
<div
|
|
|
|
|
|
className="fullview-zone fullview-zone-right"
|
|
|
|
|
|
onClick={(e) => {
|
|
|
|
|
|
e.stopPropagation()
|
|
|
|
|
|
if (currentImageIndex < images.length - 1) {
|
|
|
|
|
|
handleNext()
|
|
|
|
|
|
}
|
|
|
|
|
|
}}
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Кнопки навигации - всегда видимые, но неактивные когда нельзя переключить */}
|
|
|
|
|
|
<button
|
|
|
|
|
|
className="fullview-nav-btn prev"
|
|
|
|
|
|
onClick={(e) => { e.stopPropagation(); handlePrev(); }}
|
|
|
|
|
|
disabled={currentImageIndex === 0}
|
|
|
|
|
|
style={{ opacity: currentImageIndex === 0 ? 0.3 : 1 }}
|
|
|
|
|
|
>
|
|
|
|
|
|
<ChevronLeft size={32} />
|
|
|
|
|
|
</button>
|
2025-11-21 01:14:56 +00:00
|
|
|
|
|
2025-12-08 15:36:51 +00:00
|
|
|
|
<button
|
|
|
|
|
|
className="fullview-nav-btn next"
|
|
|
|
|
|
onClick={(e) => { e.stopPropagation(); handleNext(); }}
|
|
|
|
|
|
disabled={currentImageIndex === images.length - 1}
|
|
|
|
|
|
style={{ opacity: currentImageIndex === images.length - 1 ? 0.3 : 1 }}
|
|
|
|
|
|
>
|
|
|
|
|
|
<ChevronRight size={32} />
|
|
|
|
|
|
</button>
|
2025-11-21 01:14:56 +00:00
|
|
|
|
</>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{images.length > 1 && (
|
2025-12-04 20:11:28 +00:00
|
|
|
|
<div className="fullview-dots" onClick={(e) => e.stopPropagation()}>
|
2025-11-21 01:14:56 +00:00
|
|
|
|
{images.map((_, index) => (
|
|
|
|
|
|
<span
|
|
|
|
|
|
key={index}
|
|
|
|
|
|
className={`fullview-dot ${index === currentImageIndex ? 'active' : ''}`}
|
|
|
|
|
|
onClick={(e) => { e.stopPropagation(); setCurrentImageIndex(index); }}
|
|
|
|
|
|
/>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
2025-12-04 20:27:45 +00:00
|
|
|
|
</div>,
|
|
|
|
|
|
document.body
|
2025-11-21 01:14:56 +00:00
|
|
|
|
)}
|
2025-12-04 20:00:39 +00:00
|
|
|
|
|
2025-12-04 20:27:45 +00:00
|
|
|
|
{/* Модалка комментариев через Portal */}
|
|
|
|
|
|
{showComments && createPortal(
|
2025-12-04 20:00:39 +00:00
|
|
|
|
<CommentsModal
|
|
|
|
|
|
post={post}
|
2025-12-04 21:45:02 +00:00
|
|
|
|
currentUser={currentUser}
|
2025-12-04 20:00:39 +00:00
|
|
|
|
onClose={() => setShowComments(false)}
|
|
|
|
|
|
onUpdate={onUpdate}
|
2025-12-04 20:27:45 +00:00
|
|
|
|
/>,
|
|
|
|
|
|
document.body
|
2025-12-04 20:00:39 +00:00
|
|
|
|
)}
|
|
|
|
|
|
|
2025-12-04 20:27:45 +00:00
|
|
|
|
{/* Модалка меню через Portal */}
|
|
|
|
|
|
{showMenu && createPortal(
|
2025-12-04 20:00:39 +00:00
|
|
|
|
<PostMenu
|
|
|
|
|
|
post={post}
|
|
|
|
|
|
currentUser={currentUser}
|
2025-12-04 22:28:51 +00:00
|
|
|
|
onClose={() => {
|
|
|
|
|
|
setShowMenu(false)
|
|
|
|
|
|
setMenuButtonPosition(null)
|
|
|
|
|
|
}}
|
2025-12-04 20:00:39 +00:00
|
|
|
|
onDelete={async () => {
|
|
|
|
|
|
await handleDelete()
|
|
|
|
|
|
setShowMenu(false)
|
2025-12-04 22:28:51 +00:00
|
|
|
|
setMenuButtonPosition(null)
|
2025-12-04 20:00:39 +00:00
|
|
|
|
}}
|
2025-12-04 22:28:51 +00:00
|
|
|
|
onUpdate={onUpdate}
|
|
|
|
|
|
buttonPosition={menuButtonPosition}
|
2025-12-04 20:27:45 +00:00
|
|
|
|
/>,
|
|
|
|
|
|
document.body
|
2025-12-04 20:00:39 +00:00
|
|
|
|
)}
|
2025-11-03 20:35:01 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|