Update files
This commit is contained in:
parent
0943fd1d19
commit
10d909711d
|
|
@ -29,6 +29,7 @@ export default function PostCard({ post, currentUser, onUpdate }) {
|
||||||
const [showFullView, setShowFullView] = useState(false)
|
const [showFullView, setShowFullView] = useState(false)
|
||||||
const [showComments, setShowComments] = useState(false)
|
const [showComments, setShowComments] = useState(false)
|
||||||
const [showMenu, setShowMenu] = useState(false)
|
const [showMenu, setShowMenu] = useState(false)
|
||||||
|
const [menuButtonPosition, setMenuButtonPosition] = useState(null)
|
||||||
|
|
||||||
// Проверка на существование автора
|
// Проверка на существование автора
|
||||||
if (!post.author) {
|
if (!post.author) {
|
||||||
|
|
@ -168,8 +169,17 @@ export default function PostCard({ post, currentUser, onUpdate }) {
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className="menu-btn"
|
className="menu-btn"
|
||||||
|
data-menu-button={post._id}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
|
const button = e.currentTarget
|
||||||
|
const rect = button.getBoundingClientRect()
|
||||||
|
setMenuButtonPosition({
|
||||||
|
top: rect.top,
|
||||||
|
left: rect.left,
|
||||||
|
bottom: rect.bottom,
|
||||||
|
right: rect.right
|
||||||
|
})
|
||||||
setShowMenu(true)
|
setShowMenu(true)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -346,11 +356,17 @@ export default function PostCard({ post, currentUser, onUpdate }) {
|
||||||
<PostMenu
|
<PostMenu
|
||||||
post={post}
|
post={post}
|
||||||
currentUser={currentUser}
|
currentUser={currentUser}
|
||||||
onClose={() => setShowMenu(false)}
|
onClose={() => {
|
||||||
|
setShowMenu(false)
|
||||||
|
setMenuButtonPosition(null)
|
||||||
|
}}
|
||||||
onDelete={async () => {
|
onDelete={async () => {
|
||||||
await handleDelete()
|
await handleDelete()
|
||||||
setShowMenu(false)
|
setShowMenu(false)
|
||||||
|
setMenuButtonPosition(null)
|
||||||
}}
|
}}
|
||||||
|
onUpdate={onUpdate}
|
||||||
|
buttonPosition={menuButtonPosition}
|
||||||
/>,
|
/>,
|
||||||
document.body
|
document.body
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,11 @@
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
background: var(--bg-secondary);
|
background: transparent;
|
||||||
z-index: 10500;
|
z-index: 10500;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
align-items: flex-start;
|
||||||
padding: 16px;
|
justify-content: flex-start;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
touch-action: none;
|
touch-action: none;
|
||||||
overscroll-behavior: contain;
|
overscroll-behavior: contain;
|
||||||
|
|
@ -29,75 +29,149 @@
|
||||||
overscroll-behavior: contain;
|
overscroll-behavior: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-header {
|
.menu-content {
|
||||||
display: flex;
|
background: var(--bg-secondary);
|
||||||
align-items: center;
|
border-radius: 12px;
|
||||||
justify-content: space-between;
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
padding-bottom: 16px;
|
padding: 4px;
|
||||||
border-bottom: 1px solid var(--divider-color);
|
margin-top: 8px;
|
||||||
|
min-width: 140px;
|
||||||
|
z-index: 10501;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-header h2 {
|
.menu-items {
|
||||||
font-size: 18px;
|
display: flex;
|
||||||
font-weight: 600;
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 12px;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item:hover {
|
||||||
|
background: var(--bg-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item:active {
|
||||||
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-close-btn {
|
.menu-close-btn {
|
||||||
width: 40px;
|
width: 24px;
|
||||||
height: 40px;
|
height: 24px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: var(--bg-primary);
|
background: transparent;
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
border: none;
|
border: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
/* Убираем pointer-events чтобы клики работали */
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-close-btn svg {
|
.menu-close-btn svg {
|
||||||
stroke: currentColor;
|
stroke: currentColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-items {
|
|
||||||
padding-top: 16px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 8px;
|
|
||||||
/* Убираем pointer-events и touch-action чтобы клики работали */
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-item {
|
|
||||||
width: 100%;
|
|
||||||
padding: 16px;
|
|
||||||
background: var(--bg-primary);
|
|
||||||
border: none;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
color: var(--text-primary);
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 500;
|
|
||||||
border-radius: 12px;
|
|
||||||
cursor: pointer;
|
|
||||||
/* Убираем pointer-events и touch-action чтобы клики работали */
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-item svg {
|
.menu-item svg {
|
||||||
stroke: currentColor;
|
stroke: currentColor;
|
||||||
}
|
flex-shrink: 0;
|
||||||
|
|
||||||
.menu-item:active {
|
|
||||||
opacity: 0.7;
|
|
||||||
transform: scale(0.98);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-item.danger {
|
.menu-item.danger {
|
||||||
color: #FF3B30;
|
color: #FF3B30;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.menu-item.danger svg {
|
||||||
|
stroke: #FF3B30;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Edit modal styles */
|
||||||
|
.edit-menu-content {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 16px;
|
||||||
|
max-width: 90vw;
|
||||||
|
width: 400px;
|
||||||
|
max-height: 80vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-menu-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-menu-header h3 {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-content-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
border: 1px solid var(--divider-color);
|
||||||
|
border-radius: 12px;
|
||||||
|
background: var(--bg-primary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 1.5;
|
||||||
|
resize: none;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-menu-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-cancel-btn {
|
||||||
|
padding: 10px 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: var(--bg-primary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
border: 1px solid var(--divider-color);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-save-btn {
|
||||||
|
padding: 10px 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: var(--button-accent);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-save-btn:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
.report-modal-header {
|
.report-modal-header {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
border-bottom: 1px solid var(--divider-color);
|
border-bottom: 1px solid var(--divider-color);
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,15 @@
|
||||||
import { X, Trash2, AlertCircle, Flag } from 'lucide-react'
|
import { X, Trash2, AlertCircle, Flag, Edit2 } from 'lucide-react'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { createPortal } from 'react-dom'
|
import { createPortal } from 'react-dom'
|
||||||
import { reportPost } from '../utils/api'
|
import { reportPost, editPost } from '../utils/api'
|
||||||
import { hapticFeedback, showConfirm } from '../utils/telegram'
|
import { hapticFeedback, showConfirm } from '../utils/telegram'
|
||||||
import './PostMenu.css'
|
import './PostMenu.css'
|
||||||
|
|
||||||
export default function PostMenu({ post, currentUser, onClose, onDelete }) {
|
export default function PostMenu({ post, currentUser, onClose, onDelete, onUpdate, buttonPosition }) {
|
||||||
const [showReportModal, setShowReportModal] = useState(false)
|
const [showReportModal, setShowReportModal] = useState(false)
|
||||||
|
const [showEditModal, setShowEditModal] = useState(false)
|
||||||
const [reportReason, setReportReason] = useState('')
|
const [reportReason, setReportReason] = useState('')
|
||||||
|
const [editContent, setEditContent] = useState(post.content || '')
|
||||||
const [submitting, setSubmitting] = useState(false)
|
const [submitting, setSubmitting] = useState(false)
|
||||||
|
|
||||||
const isOwnPost = post.author._id === currentUser.id
|
const isOwnPost = post.author._id === currentUser.id
|
||||||
|
|
@ -77,6 +79,31 @@ export default function PostMenu({ post, currentUser, onClose, onDelete }) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleEdit = async () => {
|
||||||
|
if (!editContent.trim()) {
|
||||||
|
alert('Текст поста не может быть пустым')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
setSubmitting(true)
|
||||||
|
hapticFeedback('light')
|
||||||
|
await editPost(post._id, { content: editContent })
|
||||||
|
hapticFeedback('success')
|
||||||
|
setShowEditModal(false)
|
||||||
|
onClose()
|
||||||
|
if (onUpdate) {
|
||||||
|
onUpdate()
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка редактирования поста:', error)
|
||||||
|
hapticFeedback('error')
|
||||||
|
alert('Ошибка редактирования поста')
|
||||||
|
} finally {
|
||||||
|
setSubmitting(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleOverlayClick = (e) => {
|
const handleOverlayClick = (e) => {
|
||||||
// Закрывать только при клике на overlay, не на контент
|
// Закрывать только при клике на overlay, не на контент
|
||||||
if (e.target === e.currentTarget) {
|
if (e.target === e.currentTarget) {
|
||||||
|
|
@ -84,6 +111,63 @@ export default function PostMenu({ post, currentUser, onClose, onDelete }) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showEditModal) {
|
||||||
|
return createPortal(
|
||||||
|
<div
|
||||||
|
className="post-menu-overlay"
|
||||||
|
onMouseDown={(e) => e.stopPropagation()}
|
||||||
|
onTouchStart={(e) => e.stopPropagation()}
|
||||||
|
onClick={handleOverlayClick}
|
||||||
|
>
|
||||||
|
<div className="edit-menu-content" onClick={(e) => e.stopPropagation()}>
|
||||||
|
<div className="edit-menu-header">
|
||||||
|
<h3>Редактировать пост</h3>
|
||||||
|
<button className="menu-close-btn" onClick={() => setShowEditModal(false)}>
|
||||||
|
<X size={18} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<textarea
|
||||||
|
className="edit-content-input"
|
||||||
|
value={editContent}
|
||||||
|
onChange={e => setEditContent(e.target.value)}
|
||||||
|
placeholder="Текст поста..."
|
||||||
|
maxLength={2000}
|
||||||
|
rows={6}
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
<div className="edit-menu-actions">
|
||||||
|
<button
|
||||||
|
className="edit-cancel-btn"
|
||||||
|
onClick={() => {
|
||||||
|
setShowEditModal(false)
|
||||||
|
setEditContent(post.content || '')
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Отмена
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="edit-save-btn"
|
||||||
|
onClick={handleEdit}
|
||||||
|
disabled={submitting || !editContent.trim()}
|
||||||
|
>
|
||||||
|
{submitting ? 'Сохранение...' : 'Сохранить'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>,
|
||||||
|
document.body
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const menuStyle = buttonPosition ? {
|
||||||
|
position: 'fixed',
|
||||||
|
top: `${buttonPosition.bottom + 4}px`,
|
||||||
|
left: `${buttonPosition.left + (buttonPosition.right - buttonPosition.left) / 2}px`,
|
||||||
|
transform: 'translateX(-50%)',
|
||||||
|
width: 'auto',
|
||||||
|
minWidth: '140px'
|
||||||
|
} : {}
|
||||||
|
|
||||||
return createPortal(
|
return createPortal(
|
||||||
<div
|
<div
|
||||||
className="post-menu-overlay"
|
className="post-menu-overlay"
|
||||||
|
|
@ -91,31 +175,26 @@ export default function PostMenu({ post, currentUser, onClose, onDelete }) {
|
||||||
onTouchStart={(e) => e.stopPropagation()}
|
onTouchStart={(e) => e.stopPropagation()}
|
||||||
onClick={handleOverlayClick}
|
onClick={handleOverlayClick}
|
||||||
>
|
>
|
||||||
<div className="menu-header" onClick={(e) => e.stopPropagation()}>
|
<div className="menu-content" style={menuStyle} onClick={(e) => e.stopPropagation()}>
|
||||||
<button className="menu-close-btn" onClick={onClose}>
|
<div className="menu-items" onClick={(e) => e.stopPropagation()}>
|
||||||
<X size={24} />
|
{isOwnPost || isModerator ? (
|
||||||
</button>
|
<>
|
||||||
<h2>Действия</h2>
|
<button className="menu-item" onClick={() => setShowEditModal(true)}>
|
||||||
<div style={{ width: 40 }} />
|
<Edit2 size={18} />
|
||||||
</div>
|
<span>Редактировать</span>
|
||||||
|
</button>
|
||||||
<div className="menu-items" onClick={(e) => e.stopPropagation()}>
|
<button className="menu-item danger" onClick={onDelete}>
|
||||||
{isOwnPost || isModerator ? (
|
<Trash2 size={18} />
|
||||||
<button className="menu-item danger" onClick={onDelete}>
|
<span>Удалить</span>
|
||||||
<Trash2 size={20} />
|
</button>
|
||||||
<span>Удалить пост</span>
|
</>
|
||||||
</button>
|
) : (
|
||||||
) : (
|
<button className="menu-item" onClick={() => setShowReportModal(true)}>
|
||||||
<button className="menu-item" onClick={() => setShowReportModal(true)}>
|
<Flag size={18} />
|
||||||
<Flag size={20} />
|
<span>Пожаловаться</span>
|
||||||
<span>Пожаловаться</span>
|
</button>
|
||||||
</button>
|
)}
|
||||||
)}
|
</div>
|
||||||
|
|
||||||
<button className="menu-item" onClick={onClose}>
|
|
||||||
<X size={20} />
|
|
||||||
<span>Отмена</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>,
|
</div>,
|
||||||
document.body
|
document.body
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue