Update files
This commit is contained in:
parent
cf953709ff
commit
ed8917e8dd
|
|
@ -57,7 +57,7 @@ function createProxyUrl(originalUrl) {
|
||||||
|
|
||||||
// Эндпоинт для проксирования изображений
|
// Эндпоинт для проксирования изображений
|
||||||
// Используем более мягкий rate limiter для прокси
|
// Используем более мягкий rate limiter для прокси
|
||||||
router.get('/proxy/:encodedUrl', proxyLimiter, async (req, res) => {
|
router.get('/proxy/:encodedUrl', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { encodedUrl } = req.params;
|
const { encodedUrl } = req.params;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -115,6 +115,35 @@
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.loading-state {
|
||||||
|
padding: 40px 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-state .spinner {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border: 3px solid var(--divider-color);
|
||||||
|
border-top-color: var(--button-accent);
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 0.8s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-state p {
|
||||||
|
font-size: 14px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.empty-comments {
|
.empty-comments {
|
||||||
padding: 60px 20px;
|
padding: 60px 20px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { useState } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { createPortal } from 'react-dom'
|
import { createPortal } from 'react-dom'
|
||||||
import { X, Send } from 'lucide-react'
|
import { X, Send } from 'lucide-react'
|
||||||
import { commentPost } from '../utils/api'
|
import { commentPost, getPosts } from '../utils/api'
|
||||||
import { hapticFeedback } from '../utils/telegram'
|
import { hapticFeedback } from '../utils/telegram'
|
||||||
import { decodeHtmlEntities } from '../utils/htmlEntities'
|
import { decodeHtmlEntities } from '../utils/htmlEntities'
|
||||||
import './CommentsModal.css'
|
import './CommentsModal.css'
|
||||||
|
|
@ -9,7 +9,50 @@ import './CommentsModal.css'
|
||||||
export default function CommentsModal({ post, onClose, onUpdate }) {
|
export default function CommentsModal({ post, onClose, onUpdate }) {
|
||||||
const [comment, setComment] = useState('')
|
const [comment, setComment] = useState('')
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [comments, setComments] = useState(post.comments || [])
|
const [comments, setComments] = useState([])
|
||||||
|
const [fullPost, setFullPost] = useState(post)
|
||||||
|
const [loadingPost, setLoadingPost] = useState(false)
|
||||||
|
|
||||||
|
// Загрузить полные данные поста с комментариями
|
||||||
|
useEffect(() => {
|
||||||
|
if (post?._id) {
|
||||||
|
loadFullPost()
|
||||||
|
} else {
|
||||||
|
setFullPost(post)
|
||||||
|
setComments(post?.comments || [])
|
||||||
|
}
|
||||||
|
}, [post?._id])
|
||||||
|
|
||||||
|
const loadFullPost = async () => {
|
||||||
|
try {
|
||||||
|
setLoadingPost(true)
|
||||||
|
const response = await getPosts()
|
||||||
|
const foundPost = response.posts?.find(p => p._id === post._id)
|
||||||
|
if (foundPost) {
|
||||||
|
setFullPost(foundPost)
|
||||||
|
setComments(foundPost.comments || [])
|
||||||
|
} else {
|
||||||
|
// Fallback на переданный post
|
||||||
|
setFullPost(post)
|
||||||
|
setComments(post?.comments || [])
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[CommentsModal] Ошибка загрузки поста:', error)
|
||||||
|
// Fallback на переданный post
|
||||||
|
setFullPost(post)
|
||||||
|
setComments(post?.comments || [])
|
||||||
|
} finally {
|
||||||
|
setLoadingPost(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверка на существование поста
|
||||||
|
if (!post) {
|
||||||
|
console.error('[CommentsModal] Post is missing')
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const displayPost = fullPost || post
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (!comment.trim()) return
|
if (!comment.trim()) return
|
||||||
|
|
@ -19,9 +62,11 @@ export default function CommentsModal({ post, onClose, onUpdate }) {
|
||||||
hapticFeedback('light')
|
hapticFeedback('light')
|
||||||
|
|
||||||
const result = await commentPost(post._id, comment)
|
const result = await commentPost(post._id, comment)
|
||||||
setComments(result.comments)
|
setComments(result.comments || [])
|
||||||
setComment('')
|
setComment('')
|
||||||
hapticFeedback('success')
|
hapticFeedback('success')
|
||||||
|
// Обновить полный пост
|
||||||
|
await loadFullPost()
|
||||||
onUpdate()
|
onUpdate()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Ошибка добавления комментария:', error)
|
console.error('Ошибка добавления комментария:', error)
|
||||||
|
|
@ -50,6 +95,10 @@ export default function CommentsModal({ post, onClose, onUpdate }) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!post) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
return createPortal(
|
return createPortal(
|
||||||
<div
|
<div
|
||||||
className="comments-modal-overlay"
|
className="comments-modal-overlay"
|
||||||
|
|
@ -68,33 +117,42 @@ export default function CommentsModal({ post, onClose, onUpdate }) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Пост */}
|
{/* Пост */}
|
||||||
|
{loadingPost ? (
|
||||||
|
<div className="post-preview">
|
||||||
|
<div className="loading-state">
|
||||||
|
<div className="spinner" />
|
||||||
|
<p>Загрузка...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
<div className="post-preview">
|
<div className="post-preview">
|
||||||
<div className="preview-author">
|
<div className="preview-author">
|
||||||
<img
|
<img
|
||||||
src={post.author?.photoUrl || '/default-avatar.png'}
|
src={displayPost.author?.photoUrl || '/default-avatar.png'}
|
||||||
alt={post.author?.username || post.author?.firstName || 'User'}
|
alt={displayPost.author?.username || displayPost.author?.firstName || 'User'}
|
||||||
className="preview-avatar"
|
className="preview-avatar"
|
||||||
onError={(e) => { e.target.src = '/default-avatar.png' }}
|
onError={(e) => { e.target.src = '/default-avatar.png' }}
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<div className="preview-name">
|
<div className="preview-name">
|
||||||
{post.author?.firstName || ''} {post.author?.lastName || ''}
|
{displayPost.author?.firstName || ''} {displayPost.author?.lastName || ''}
|
||||||
{!post.author?.firstName && !post.author?.lastName && 'Пользователь'}
|
{!displayPost.author?.firstName && !displayPost.author?.lastName && 'Пользователь'}
|
||||||
</div>
|
</div>
|
||||||
<div className="preview-username">@{post.author?.username || post.author?.firstName || 'user'}</div>
|
<div className="preview-username">@{displayPost.author?.username || displayPost.author?.firstName || 'user'}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{post.content && (
|
{displayPost.content && (
|
||||||
<div className="preview-content">{decodeHtmlEntities(post.content)}</div>
|
<div className="preview-content">{decodeHtmlEntities(displayPost.content)}</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{((post.images && post.images.length > 0) || post.imageUrl) && (
|
{((displayPost.images && displayPost.images.length > 0) || displayPost.imageUrl) && (
|
||||||
<div className="preview-image">
|
<div className="preview-image">
|
||||||
<img src={post.images?.[0] || post.imageUrl} alt="Post" />
|
<img src={displayPost.images?.[0] || displayPost.imageUrl} alt="Post" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Список комментариев */}
|
{/* Список комментариев */}
|
||||||
<div className="comments-list">
|
<div className="comments-list">
|
||||||
|
|
|
||||||
|
|
@ -49,13 +49,13 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 16px;
|
padding: 12px 16px;
|
||||||
border-bottom: 1px solid var(--divider-color);
|
border-bottom: 1px solid var(--divider-color);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.follow-list-header h2 {
|
.follow-list-header h2 {
|
||||||
font-size: 18px;
|
font-size: 16px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
@ -102,13 +102,21 @@
|
||||||
padding: 8px 0;
|
padding: 8px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.user-item-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.user-item {
|
.user-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 12px;
|
gap: 8px;
|
||||||
padding: 12px 16px;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background 0.2s;
|
transition: background 0.2s;
|
||||||
|
padding: 4px;
|
||||||
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-item:active {
|
.user-item:active {
|
||||||
|
|
@ -116,8 +124,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-avatar {
|
.user-avatar {
|
||||||
width: 48px;
|
width: 36px;
|
||||||
height: 48px;
|
height: 36px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
|
@ -129,7 +137,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-name {
|
.user-name {
|
||||||
font-size: 15px;
|
font-size: 14px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
margin-bottom: 2px;
|
margin-bottom: 2px;
|
||||||
|
|
@ -139,7 +147,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-username {
|
.user-username {
|
||||||
font-size: 13px;
|
font-size: 12px;
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
@ -150,21 +158,22 @@
|
||||||
.follow-btn {
|
.follow-btn {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 6px;
|
justify-content: center;
|
||||||
padding: 8px 16px;
|
gap: 8px;
|
||||||
border-radius: 20px;
|
width: 100%;
|
||||||
|
padding: 10px 18px;
|
||||||
|
border-radius: 12px;
|
||||||
background: var(--button-accent);
|
background: var(--button-accent);
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: opacity 0.2s;
|
transition: opacity 0.2s ease;
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.follow-btn:active {
|
.follow-btn:active {
|
||||||
opacity: 0.8;
|
opacity: 0.85;
|
||||||
}
|
}
|
||||||
|
|
||||||
.follow-btn.following {
|
.follow-btn.following {
|
||||||
|
|
|
||||||
|
|
@ -87,8 +87,8 @@ export default function FollowListModal({ users, title, onClose, currentUser })
|
||||||
const isFollowing = userStates[user._id]?.isFollowing || false
|
const isFollowing = userStates[user._id]?.isFollowing || false
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div key={user._id} className="user-item-wrapper">
|
||||||
<div
|
<div
|
||||||
key={user._id}
|
|
||||||
className="user-item"
|
className="user-item"
|
||||||
onClick={() => handleUserClick(user._id)}
|
onClick={() => handleUserClick(user._id)}
|
||||||
>
|
>
|
||||||
|
|
@ -105,6 +105,7 @@ export default function FollowListModal({ users, title, onClose, currentUser })
|
||||||
</div>
|
</div>
|
||||||
<div className="user-username">@{user.username || user.firstName || 'user'}</div>
|
<div className="user-username">@{user.username || user.firstName || 'user'}</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{!isOwnProfile && (
|
{!isOwnProfile && (
|
||||||
<button
|
<button
|
||||||
|
|
@ -113,12 +114,12 @@ export default function FollowListModal({ users, title, onClose, currentUser })
|
||||||
>
|
>
|
||||||
{isFollowing ? (
|
{isFollowing ? (
|
||||||
<>
|
<>
|
||||||
<UserMinus size={16} />
|
<UserMinus size={18} />
|
||||||
<span>Отписаться</span>
|
<span>Отписаться</span>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<UserPlus size={16} />
|
<UserPlus size={18} />
|
||||||
<span>Подписаться</span>
|
<span>Подписаться</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -475,3 +475,48 @@
|
||||||
color: rgba(255, 255, 255, 0.7);
|
color: rgba(255, 255, 255, 0.7);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.load-more-container {
|
||||||
|
padding: 20px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.load-more-btn {
|
||||||
|
padding: 12px 24px;
|
||||||
|
border-radius: 24px;
|
||||||
|
background: var(--button-accent);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.load-more-btn:active {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-more {
|
||||||
|
padding: 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-more .spinner {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border: 3px solid var(--divider-color);
|
||||||
|
border-top-color: var(--button-accent);
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 0.8s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-more p {
|
||||||
|
font-size: 14px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,11 @@ export default function Search({ user }) {
|
||||||
const [query, setQuery] = useState('')
|
const [query, setQuery] = useState('')
|
||||||
const [results, setResults] = useState([])
|
const [results, setResults] = useState([])
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
|
const [loadingMore, setLoadingMore] = useState(false)
|
||||||
const [tagSuggestions, setTagSuggestions] = useState([])
|
const [tagSuggestions, setTagSuggestions] = useState([])
|
||||||
|
const [showTagSuggestions, setShowTagSuggestions] = useState(true)
|
||||||
|
const [currentPage, setCurrentPage] = useState(1)
|
||||||
|
const [hasMore, setHasMore] = useState(false)
|
||||||
const [currentIndex, setCurrentIndex] = useState(0)
|
const [currentIndex, setCurrentIndex] = useState(0)
|
||||||
const [showViewer, setShowViewer] = useState(false)
|
const [showViewer, setShowViewer] = useState(false)
|
||||||
const [selectedImages, setSelectedImages] = useState([])
|
const [selectedImages, setSelectedImages] = useState([])
|
||||||
|
|
@ -30,12 +34,12 @@ const isVideoUrl = (url = '') => {
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (query.length > 1) {
|
if (query.length > 1 && showTagSuggestions) {
|
||||||
loadTagSuggestions()
|
loadTagSuggestions()
|
||||||
} else {
|
} else {
|
||||||
setTagSuggestions([])
|
setTagSuggestions([])
|
||||||
}
|
}
|
||||||
}, [query, mode])
|
}, [query, mode, showTagSuggestions])
|
||||||
|
|
||||||
const loadTagSuggestions = async () => {
|
const loadTagSuggestions = async () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -90,21 +94,29 @@ const isVideoUrl = (url = '') => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSearch = async (searchQuery = query) => {
|
const handleSearch = async (searchQuery = query, page = 1, append = false) => {
|
||||||
if (!searchQuery.trim()) return
|
if (!searchQuery.trim()) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (page === 1) {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
hapticFeedback('light')
|
|
||||||
setResults([])
|
setResults([])
|
||||||
|
} else {
|
||||||
|
setLoadingMore(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
hapticFeedback('light')
|
||||||
|
setShowTagSuggestions(false)
|
||||||
|
|
||||||
let allResults = []
|
let allResults = []
|
||||||
|
let hasMoreResults = false
|
||||||
|
|
||||||
if (mode === 'furry') {
|
if (mode === 'furry') {
|
||||||
try {
|
try {
|
||||||
const furryResults = await searchFurry(searchQuery, { limit: 320, page: 1 })
|
const furryResults = await searchFurry(searchQuery, { limit: 320, page })
|
||||||
if (Array.isArray(furryResults)) {
|
if (Array.isArray(furryResults)) {
|
||||||
allResults = [...allResults, ...furryResults]
|
allResults = [...allResults, ...furryResults]
|
||||||
|
hasMoreResults = furryResults.length === 320
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Ошибка e621 поиска:', error)
|
console.error('Ошибка e621 поиска:', error)
|
||||||
|
|
@ -113,29 +125,47 @@ const isVideoUrl = (url = '') => {
|
||||||
|
|
||||||
if (mode === 'anime') {
|
if (mode === 'anime') {
|
||||||
try {
|
try {
|
||||||
const animeResults = await searchAnime(searchQuery, { limit: 320, page: 1 })
|
const animeResults = await searchAnime(searchQuery, { limit: 320, page })
|
||||||
if (Array.isArray(animeResults)) {
|
if (Array.isArray(animeResults)) {
|
||||||
allResults = [...allResults, ...animeResults]
|
allResults = [...allResults, ...animeResults]
|
||||||
|
hasMoreResults = animeResults.length === 320
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Ошибка Gelbooru поиска:', error)
|
console.error('Ошибка Gelbooru поиска:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (append) {
|
||||||
|
setResults(prev => [...prev, ...allResults])
|
||||||
|
} else {
|
||||||
setResults(allResults)
|
setResults(allResults)
|
||||||
|
setCurrentPage(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
setHasMore(hasMoreResults)
|
||||||
|
setCurrentPage(page)
|
||||||
setTagSuggestions([])
|
setTagSuggestions([])
|
||||||
|
|
||||||
if (allResults.length > 0) {
|
if (allResults.length > 0) {
|
||||||
hapticFeedback('success')
|
hapticFeedback('success')
|
||||||
} else {
|
} else if (page === 1) {
|
||||||
hapticFeedback('error')
|
hapticFeedback('error')
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Ошибка поиска:', error)
|
console.error('Ошибка поиска:', error)
|
||||||
hapticFeedback('error')
|
hapticFeedback('error')
|
||||||
|
if (page === 1) {
|
||||||
setResults([])
|
setResults([])
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
|
setLoadingMore(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadMore = () => {
|
||||||
|
if (!loadingMore && hasMore && query.trim()) {
|
||||||
|
handleSearch(query, currentPage + 1, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -149,6 +179,7 @@ const isVideoUrl = (url = '') => {
|
||||||
: tagName
|
: tagName
|
||||||
|
|
||||||
setQuery(newQuery)
|
setQuery(newQuery)
|
||||||
|
setShowTagSuggestions(false)
|
||||||
handleSearch(newQuery)
|
handleSearch(newQuery)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -370,8 +401,15 @@ const isVideoUrl = (url = '') => {
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Поиск по тегам..."
|
placeholder="Поиск по тегам..."
|
||||||
value={query}
|
value={query}
|
||||||
onChange={e => setQuery(e.target.value)}
|
onChange={e => {
|
||||||
onKeyPress={e => e.key === 'Enter' && handleSearch()}
|
setQuery(e.target.value)
|
||||||
|
setShowTagSuggestions(true)
|
||||||
|
}}
|
||||||
|
onKeyPress={e => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
handleSearch()
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
{query && (
|
{query && (
|
||||||
<button className="clear-btn" onClick={() => setQuery('')}>
|
<button className="clear-btn" onClick={() => setQuery('')}>
|
||||||
|
|
@ -388,7 +426,7 @@ const isVideoUrl = (url = '') => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Подсказки тегов */}
|
{/* Подсказки тегов */}
|
||||||
{tagSuggestions.length > 0 && (
|
{tagSuggestions.length > 0 && showTagSuggestions && (
|
||||||
<div className="tag-suggestions">
|
<div className="tag-suggestions">
|
||||||
{tagSuggestions.map((tag, index) => (
|
{tagSuggestions.map((tag, index) => (
|
||||||
<button
|
<button
|
||||||
|
|
@ -451,6 +489,20 @@ const isVideoUrl = (url = '') => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Кнопка загрузки дополнительных результатов */}
|
{/* Кнопка загрузки дополнительных результатов */}
|
||||||
|
{hasMore && !loadingMore && (
|
||||||
|
<div className="load-more-container">
|
||||||
|
<button className="load-more-btn" onClick={loadMore}>
|
||||||
|
Загрузить еще
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{loadingMore && (
|
||||||
|
<div className="loading-more">
|
||||||
|
<div className="spinner" />
|
||||||
|
<p>Загрузка...</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Кнопка отправки выбранных */}
|
{/* Кнопка отправки выбранных */}
|
||||||
{selectionMode && selectedImages.length > 0 && (
|
{selectionMode && selectedImages.length > 0 && (
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue