Update files
This commit is contained in:
parent
b78b11fca9
commit
02d7b1a958
|
|
@ -0,0 +1,77 @@
|
||||||
|
# 🤖 Настройка Telegram бота для отправки изображений
|
||||||
|
|
||||||
|
## Как работает:
|
||||||
|
|
||||||
|
Когда пользователь нажимает "скачать" в просмотрщике изображений:
|
||||||
|
1. Изображение отправляется через backend
|
||||||
|
2. Backend использует Telegram Bot API
|
||||||
|
3. Фото приходит в ЛС с ботом пользователю
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Настройка на сервере:
|
||||||
|
|
||||||
|
### 1. Убедитесь что TELEGRAM_BOT_TOKEN установлен
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh root@ваш_IP
|
||||||
|
cd /var/www/nakama
|
||||||
|
nano .env
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверьте строку:
|
||||||
|
```
|
||||||
|
TELEGRAM_BOT_TOKEN=ваш_реальный_токен_от_BotFather
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Пользователь должен написать боту /start
|
||||||
|
|
||||||
|
Когда пользователь впервые откроет Mini App:
|
||||||
|
- Бот автоматически получит доступ для отправки сообщений
|
||||||
|
- Или пользователь должен написать боту `/start` один раз
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API Endpoint:
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /api/bot/send-photo
|
||||||
|
{
|
||||||
|
"userId": "123456789",
|
||||||
|
"photoUrl": "https://example.com/image.jpg",
|
||||||
|
"caption": "Описание изображения"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Как использовать:
|
||||||
|
|
||||||
|
1. Пользователь ищет изображение в поиске (e621/gelbooru)
|
||||||
|
2. Открывает просмотрщик (нажимает на картинку)
|
||||||
|
3. Нажимает кнопку "Скачать" (Download)
|
||||||
|
4. Изображение приходит в ЛС с ботом! ✅
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Альтернатива (если бот не настроен):
|
||||||
|
|
||||||
|
Если `TELEGRAM_BOT_TOKEN` не установлен:
|
||||||
|
- Fallback на обычное скачивание через браузер
|
||||||
|
- Работает без бота
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Обновление:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Загрузить новые файлы
|
||||||
|
scp backend/bot.js root@ваш_IP:/var/www/nakama/backend/
|
||||||
|
scp backend/routes/bot.js root@ваш_IP:/var/www/nakama/backend/routes/
|
||||||
|
scp backend/server.js root@ваш_IP:/var/www/nakama/backend/
|
||||||
|
|
||||||
|
# Перезапустить backend
|
||||||
|
ssh root@ваш_IP
|
||||||
|
pm2 restart nakama-backend
|
||||||
|
```
|
||||||
|
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
// Telegram Bot для отправки изображений в ЛС
|
||||||
|
const axios = require('axios');
|
||||||
|
const config = require('./config');
|
||||||
|
|
||||||
|
const TELEGRAM_API = `https://api.telegram.org/bot${config.telegramBotToken}`;
|
||||||
|
|
||||||
|
// Отправить одно фото пользователю
|
||||||
|
async function sendPhotoToUser(userId, photoUrl, caption) {
|
||||||
|
try {
|
||||||
|
const response = await axios.post(`${TELEGRAM_API}/sendPhoto`, {
|
||||||
|
chat_id: userId,
|
||||||
|
photo: photoUrl,
|
||||||
|
caption: caption || '',
|
||||||
|
parse_mode: 'HTML'
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка отправки фото:', error.response?.data || error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Отправить несколько фото группой (до 10 штук)
|
||||||
|
async function sendPhotosToUser(userId, photos) {
|
||||||
|
try {
|
||||||
|
// Telegram поддерживает до 10 фото в одной группе
|
||||||
|
const batches = [];
|
||||||
|
for (let i = 0; i < photos.length; i += 10) {
|
||||||
|
batches.push(photos.slice(i, i + 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
const results = [];
|
||||||
|
|
||||||
|
for (const batch of batches) {
|
||||||
|
const media = batch.map((photo, index) => ({
|
||||||
|
type: 'photo',
|
||||||
|
media: photo.url,
|
||||||
|
caption: index === 0 ? `<b>Из NakamaSpace</b>\n${batch.length} фото` : undefined,
|
||||||
|
parse_mode: 'HTML'
|
||||||
|
}));
|
||||||
|
|
||||||
|
const response = await axios.post(`${TELEGRAM_API}/sendMediaGroup`, {
|
||||||
|
chat_id: userId,
|
||||||
|
media: media
|
||||||
|
});
|
||||||
|
|
||||||
|
results.push(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка отправки фото группой:', error.response?.data || error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обработать данные от Web App
|
||||||
|
async function handleWebAppData(userId, dataString) {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(dataString);
|
||||||
|
|
||||||
|
if (data.action === 'send_image') {
|
||||||
|
const caption = `<b>Из NakamaSpace</b>\n\n${data.caption || ''}`;
|
||||||
|
await sendPhotoToUser(userId, data.url, caption);
|
||||||
|
return { success: true, message: 'Изображение отправлено!' };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: false, message: 'Неизвестное действие' };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка обработки данных:', error);
|
||||||
|
return { success: false, message: error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
sendPhotoToUser,
|
||||||
|
sendPhotosToUser,
|
||||||
|
handleWebAppData
|
||||||
|
};
|
||||||
|
|
||||||
|
|
@ -32,7 +32,8 @@ const PostSchema = new mongoose.Schema({
|
||||||
lowercase: true,
|
lowercase: true,
|
||||||
trim: true
|
trim: true
|
||||||
}],
|
}],
|
||||||
imageUrl: String,
|
imageUrl: String, // Старое поле для совместимости
|
||||||
|
images: [String], // Новое поле - массив изображений
|
||||||
tags: [{
|
tags: [{
|
||||||
type: String,
|
type: String,
|
||||||
enum: ['furry', 'anime', 'other'],
|
enum: ['furry', 'anime', 'other'],
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const { sendPhotoToUser, sendPhotosToUser } = require('../bot');
|
||||||
|
const { authenticate } = require('../middleware/auth');
|
||||||
|
|
||||||
|
// Endpoint для отправки одного фото в ЛС
|
||||||
|
router.post('/send-photo', authenticate, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { userId, photoUrl, caption } = req.body;
|
||||||
|
|
||||||
|
if (!userId || !photoUrl) {
|
||||||
|
return res.status(400).json({ error: 'userId и photoUrl обязательны' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await sendPhotoToUser(userId, photoUrl, caption);
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
message: 'Изображение отправлено в ваш Telegram',
|
||||||
|
result
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка отправки:', error);
|
||||||
|
res.status(500).json({
|
||||||
|
error: 'Ошибка отправки изображения',
|
||||||
|
details: error.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Endpoint для отправки нескольких фото группой
|
||||||
|
router.post('/send-photos', authenticate, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { userId, photos } = req.body;
|
||||||
|
|
||||||
|
if (!userId || !photos || !Array.isArray(photos) || photos.length === 0) {
|
||||||
|
return res.status(400).json({ error: 'userId и массив photos обязательны' });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (photos.length > 50) {
|
||||||
|
return res.status(400).json({ error: 'Максимум 50 фото за раз' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const results = await sendPhotosToUser(userId, photos);
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
message: `${photos.length} изображений отправлено в ваш Telegram`,
|
||||||
|
results
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка отправки фото:', error);
|
||||||
|
res.status(500).json({
|
||||||
|
error: 'Ошибка отправки изображений',
|
||||||
|
details: error.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
|
|
||||||
|
|
@ -41,6 +41,9 @@ const upload = multer({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Поддержка до 5 изображений в одном посте
|
||||||
|
const uploadMultiple = upload.array('images', 5);
|
||||||
|
|
||||||
// Получить ленту постов
|
// Получить ленту постов
|
||||||
router.get('/', authenticate, async (req, res) => {
|
router.get('/', authenticate, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -91,9 +94,9 @@ router.get('/', authenticate, async (req, res) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Создать пост
|
// Создать пост
|
||||||
router.post('/', authenticate, postCreationLimiter, upload.single('image'), async (req, res) => {
|
router.post('/', authenticate, postCreationLimiter, uploadMultiple, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { content, tags, mentionedUsers, isNSFW } = req.body;
|
const { content, tags, mentionedUsers, isNSFW, externalImages } = req.body;
|
||||||
|
|
||||||
// Проверка тегов
|
// Проверка тегов
|
||||||
const parsedTags = JSON.parse(tags || '[]');
|
const parsedTags = JSON.parse(tags || '[]');
|
||||||
|
|
@ -104,10 +107,28 @@ router.post('/', authenticate, postCreationLimiter, upload.single('image'), asyn
|
||||||
// Извлечь хэштеги из контента
|
// Извлечь хэштеги из контента
|
||||||
const hashtags = extractHashtags(content);
|
const hashtags = extractHashtags(content);
|
||||||
|
|
||||||
|
// Обработка изображений
|
||||||
|
let images = [];
|
||||||
|
|
||||||
|
// Загруженные файлы
|
||||||
|
if (req.files && req.files.length > 0) {
|
||||||
|
images = req.files.map(file => `/uploads/posts/${file.filename}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Внешние изображения (из поиска)
|
||||||
|
if (externalImages) {
|
||||||
|
const externalUrls = JSON.parse(externalImages);
|
||||||
|
images = [...images, ...externalUrls];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обратная совместимость - imageUrl для первого изображения
|
||||||
|
const imageUrl = images.length > 0 ? images[0] : null;
|
||||||
|
|
||||||
const post = new Post({
|
const post = new Post({
|
||||||
author: req.user._id,
|
author: req.user._id,
|
||||||
content,
|
content,
|
||||||
imageUrl: req.file ? `/uploads/posts/${req.file.filename}` : null,
|
imageUrl, // Для совместимости
|
||||||
|
images, // Новое поле
|
||||||
tags: parsedTags,
|
tags: parsedTags,
|
||||||
hashtags,
|
hashtags,
|
||||||
mentionedUsers: mentionedUsers ? JSON.parse(mentionedUsers) : [],
|
mentionedUsers: mentionedUsers ? JSON.parse(mentionedUsers) : [],
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,7 @@ app.use('/api/search', require('./routes/search'));
|
||||||
app.use('/api/search/posts', require('./routes/postSearch'));
|
app.use('/api/search/posts', require('./routes/postSearch'));
|
||||||
app.use('/api/moderation', require('./routes/moderation'));
|
app.use('/api/moderation', require('./routes/moderation'));
|
||||||
app.use('/api/statistics', require('./routes/statistics'));
|
app.use('/api/statistics', require('./routes/statistics'));
|
||||||
|
app.use('/api/bot', require('./routes/bot'));
|
||||||
|
|
||||||
// Базовый роут
|
// Базовый роут
|
||||||
app.get('/', (req, res) => {
|
app.get('/', (req, res) => {
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,10 @@
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
background: var(--bg-secondary);
|
background: var(--bg-secondary);
|
||||||
z-index: 9999;
|
z-index: 999; /* Выше навигации (50) */
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
touch-action: none;
|
touch-action: none;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.comments-modal {
|
.comments-modal {
|
||||||
|
|
@ -172,15 +173,16 @@
|
||||||
|
|
||||||
.comment-form {
|
.comment-form {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 80px;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
padding: 12px 16px;
|
padding: 12px 16px;
|
||||||
|
padding-bottom: calc(12px + 80px); /* Отступ для навигации */
|
||||||
background: var(--bg-secondary);
|
background: var(--bg-secondary);
|
||||||
border-top: 1px solid var(--divider-color);
|
border-top: 1px solid var(--divider-color);
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
z-index: 10000;
|
z-index: 1000;
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
touch-action: auto;
|
touch-action: auto;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -95,30 +95,60 @@
|
||||||
resize: vertical;
|
resize: vertical;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.images-preview {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.image-preview {
|
.image-preview {
|
||||||
position: relative;
|
position: relative;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
aspect-ratio: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-preview img {
|
.image-preview img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-height: 300px;
|
height: 100%;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
.remove-image-btn {
|
.remove-image-btn {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 8px;
|
top: 4px;
|
||||||
right: 8px;
|
right: 4px;
|
||||||
width: 32px;
|
width: 24px;
|
||||||
height: 32px;
|
height: 24px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: rgba(0, 0, 0, 0.6);
|
background: rgba(0, 0, 0, 0.7);
|
||||||
color: white;
|
color: white;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-icon-btn {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-count {
|
||||||
|
position: absolute;
|
||||||
|
top: -4px;
|
||||||
|
right: -4px;
|
||||||
|
background: #1C1C1E;
|
||||||
|
color: white;
|
||||||
|
font-size: 10px;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .image-count {
|
||||||
|
background: #FFFFFF;
|
||||||
|
color: #000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tags-section {
|
.tags-section {
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,12 @@ const TAGS = [
|
||||||
{ value: 'other', label: 'Other', color: '#A0A0A0' }
|
{ value: 'other', label: 'Other', color: '#A0A0A0' }
|
||||||
]
|
]
|
||||||
|
|
||||||
export default function CreatePostModal({ user, onClose, onPostCreated }) {
|
export default function CreatePostModal({ user, onClose, onPostCreated, initialImage }) {
|
||||||
const [content, setContent] = useState('')
|
const [content, setContent] = useState('')
|
||||||
const [selectedTags, setSelectedTags] = useState([])
|
const [selectedTags, setSelectedTags] = useState([])
|
||||||
const [image, setImage] = useState(null)
|
const [images, setImages] = useState(initialImage ? [initialImage] : [])
|
||||||
const [imagePreview, setImagePreview] = useState(null)
|
const [imagePreviews, setImagePreviews] = useState(initialImage ? [initialImage] : [])
|
||||||
|
const [externalImages, setExternalImages] = useState(initialImage ? [initialImage] : [])
|
||||||
const [isNSFW, setIsNSFW] = useState(false)
|
const [isNSFW, setIsNSFW] = useState(false)
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [showUserSearch, setShowUserSearch] = useState(false)
|
const [showUserSearch, setShowUserSearch] = useState(false)
|
||||||
|
|
@ -24,26 +25,35 @@ export default function CreatePostModal({ user, onClose, onPostCreated }) {
|
||||||
const fileInputRef = useRef(null)
|
const fileInputRef = useRef(null)
|
||||||
|
|
||||||
const handleImageSelect = (e) => {
|
const handleImageSelect = (e) => {
|
||||||
const file = e.target.files[0]
|
const files = Array.from(e.target.files)
|
||||||
if (file) {
|
if (files.length === 0) return
|
||||||
setImage(file)
|
|
||||||
|
// Максимум 5 изображений
|
||||||
|
const remainingSlots = 5 - images.length
|
||||||
|
const filesToAdd = files.slice(0, remainingSlots)
|
||||||
|
|
||||||
|
filesToAdd.forEach(file => {
|
||||||
const reader = new FileReader()
|
const reader = new FileReader()
|
||||||
reader.onloadend = () => {
|
reader.onloadend = () => {
|
||||||
setImagePreview(reader.result)
|
setImagePreviews(prev => [...prev, reader.result])
|
||||||
}
|
}
|
||||||
reader.readAsDataURL(file)
|
reader.readAsDataURL(file)
|
||||||
hapticFeedback('light')
|
})
|
||||||
}
|
|
||||||
}
|
setImages(prev => [...prev, ...filesToAdd])
|
||||||
|
hapticFeedback('light')
|
||||||
|
|
||||||
const handleRemoveImage = () => {
|
|
||||||
setImage(null)
|
|
||||||
setImagePreview(null)
|
|
||||||
if (fileInputRef.current) {
|
if (fileInputRef.current) {
|
||||||
fileInputRef.current.value = ''
|
fileInputRef.current.value = ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleRemoveImage = (index) => {
|
||||||
|
setImages(prev => prev.filter((_, i) => i !== index))
|
||||||
|
setImagePreviews(prev => prev.filter((_, i) => i !== index))
|
||||||
|
setExternalImages(prev => prev.filter((_, i) => i !== index))
|
||||||
|
}
|
||||||
|
|
||||||
const toggleTag = (tag) => {
|
const toggleTag = (tag) => {
|
||||||
hapticFeedback('light')
|
hapticFeedback('light')
|
||||||
if (selectedTags.includes(tag)) {
|
if (selectedTags.includes(tag)) {
|
||||||
|
|
@ -84,7 +94,7 @@ export default function CreatePostModal({ user, onClose, onPostCreated }) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!content.trim() && !image) {
|
if (!content.trim() && images.length === 0) {
|
||||||
alert('Добавьте текст или изображение')
|
alert('Добавьте текст или изображение')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -98,8 +108,16 @@ export default function CreatePostModal({ user, onClose, onPostCreated }) {
|
||||||
formData.append('tags', JSON.stringify(selectedTags))
|
formData.append('tags', JSON.stringify(selectedTags))
|
||||||
formData.append('isNSFW', isNSFW)
|
formData.append('isNSFW', isNSFW)
|
||||||
|
|
||||||
if (image) {
|
// Добавить загруженные файлы
|
||||||
formData.append('image', image)
|
images.forEach((image, index) => {
|
||||||
|
if (image instanceof File) {
|
||||||
|
formData.append('images', image)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Добавить внешние изображения (из поиска)
|
||||||
|
if (externalImages.length > 0) {
|
||||||
|
formData.append('externalImages', JSON.stringify(externalImages))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mentionedUsers.length > 0) {
|
if (mentionedUsers.length > 0) {
|
||||||
|
|
@ -146,13 +164,17 @@ export default function CreatePostModal({ user, onClose, onPostCreated }) {
|
||||||
rows={6}
|
rows={6}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Превью изображения */}
|
{/* Превью изображений */}
|
||||||
{imagePreview && (
|
{imagePreviews.length > 0 && (
|
||||||
<div className="image-preview">
|
<div className="images-preview">
|
||||||
<img src={imagePreview} alt="Preview" />
|
{imagePreviews.map((preview, index) => (
|
||||||
<button className="remove-image-btn" onClick={handleRemoveImage}>
|
<div key={index} className="image-preview">
|
||||||
<X size={20} />
|
<img src={preview} alt={`Preview ${index + 1}`} />
|
||||||
</button>
|
<button className="remove-image-btn" onClick={() => handleRemoveImage(index)}>
|
||||||
|
<X size={16} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
@ -209,12 +231,18 @@ export default function CreatePostModal({ user, onClose, onPostCreated }) {
|
||||||
ref={fileInputRef}
|
ref={fileInputRef}
|
||||||
type="file"
|
type="file"
|
||||||
accept="image/*"
|
accept="image/*"
|
||||||
|
multiple
|
||||||
onChange={handleImageSelect}
|
onChange={handleImageSelect}
|
||||||
style={{ display: 'none' }}
|
style={{ display: 'none' }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<button className="action-icon-btn" onClick={() => fileInputRef.current?.click()}>
|
<button
|
||||||
|
className="action-icon-btn"
|
||||||
|
onClick={() => fileInputRef.current?.click()}
|
||||||
|
disabled={images.length >= 5}
|
||||||
|
>
|
||||||
<ImageIcon size={22} />
|
<ImageIcon size={22} />
|
||||||
|
{images.length > 0 && <span className="image-count">{images.length}/5</span>}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button className="action-icon-btn" onClick={() => setShowUserSearch(true)}>
|
<button className="action-icon-btn" onClick={() => setShowUserSearch(true)}>
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 8px 0 calc(8px + env(safe-area-inset-bottom));
|
padding: 8px 0 calc(8px + env(safe-area-inset-bottom));
|
||||||
z-index: 100;
|
z-index: 50; /* МЕНЬШЕ чем модалки */
|
||||||
box-shadow: 0 -2px 8px var(--shadow-sm);
|
box-shadow: 0 -2px 8px var(--shadow-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -68,20 +68,81 @@
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-image {
|
.post-images {
|
||||||
margin: 0 -16px 12px;
|
margin: 0 -16px 12px;
|
||||||
width: calc(100% + 32px);
|
width: calc(100% + 32px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-carousel {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
max-height: 400px;
|
max-height: 400px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-image img {
|
.image-carousel img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
max-height: 400px;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.carousel-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
color: white;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
backdrop-filter: blur(5px);
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-btn svg {
|
||||||
|
stroke: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-btn.prev {
|
||||||
|
left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-btn.next {
|
||||||
|
right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-dots {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 12px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(255, 255, 255, 0.5);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot.active {
|
||||||
|
background: white;
|
||||||
|
width: 20px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.post-tags {
|
.post-tags {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { Heart, MessageCircle, MoreVertical } from 'lucide-react'
|
import { Heart, MessageCircle, MoreVertical, ChevronLeft, ChevronRight } from 'lucide-react'
|
||||||
import { likePost, commentPost, deletePost } from '../utils/api'
|
import { likePost, commentPost, deletePost } from '../utils/api'
|
||||||
import { hapticFeedback, showConfirm } from '../utils/telegram'
|
import { hapticFeedback, showConfirm } from '../utils/telegram'
|
||||||
import PostMenu from './PostMenu'
|
import PostMenu from './PostMenu'
|
||||||
|
|
@ -25,6 +25,10 @@ export default function PostCard({ post, currentUser, onUpdate }) {
|
||||||
const [likesCount, setLikesCount] = useState(post.likes.length)
|
const [likesCount, setLikesCount] = useState(post.likes.length)
|
||||||
const [showMenu, setShowMenu] = useState(false)
|
const [showMenu, setShowMenu] = useState(false)
|
||||||
const [showComments, setShowComments] = useState(false)
|
const [showComments, setShowComments] = useState(false)
|
||||||
|
const [currentImageIndex, setCurrentImageIndex] = useState(0)
|
||||||
|
|
||||||
|
// Поддержка и старого поля imageUrl и нового images
|
||||||
|
const images = post.images && post.images.length > 0 ? post.images : (post.imageUrl ? [post.imageUrl] : [])
|
||||||
|
|
||||||
const handleLike = async () => {
|
const handleLike = async () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -95,10 +99,38 @@ export default function PostCard({ post, currentUser, onUpdate }) {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Изображение */}
|
{/* Изображения */}
|
||||||
{post.imageUrl && (
|
{images.length > 0 && (
|
||||||
<div className="post-image">
|
<div className="post-images">
|
||||||
<img src={post.imageUrl} alt="Post" />
|
<div className="image-carousel">
|
||||||
|
<img src={images[currentImageIndex]} alt={`Image ${currentImageIndex + 1}`} />
|
||||||
|
|
||||||
|
{images.length > 1 && (
|
||||||
|
<>
|
||||||
|
{currentImageIndex > 0 && (
|
||||||
|
<button className="carousel-btn prev" onClick={() => setCurrentImageIndex(currentImageIndex - 1)}>
|
||||||
|
<ChevronLeft size={24} />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{currentImageIndex < images.length - 1 && (
|
||||||
|
<button className="carousel-btn next" onClick={() => setCurrentImageIndex(currentImageIndex + 1)}>
|
||||||
|
<ChevronRight size={24} />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="carousel-dots">
|
||||||
|
{images.map((_, index) => (
|
||||||
|
<span
|
||||||
|
key={index}
|
||||||
|
className={`dot ${index === currentImageIndex ? 'active' : ''}`}
|
||||||
|
onClick={() => setCurrentImageIndex(index)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,13 @@
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
background: var(--bg-secondary);
|
background: var(--bg-secondary);
|
||||||
z-index: 9999;
|
z-index: 999; /* Выше навигации (50) */
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
touch-action: none;
|
touch-action: none;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.report-modal-overlay {
|
.report-modal-overlay {
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,9 @@
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
border-bottom: 1px solid var(--divider-color);
|
border-bottom: 1px solid var(--divider-color);
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-header h1 {
|
.search-header h1 {
|
||||||
|
|
@ -17,6 +20,27 @@
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.selection-toggle {
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 20px;
|
||||||
|
background: var(--bg-primary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selection-toggle.active {
|
||||||
|
background: #1C1C1E;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .selection-toggle.active {
|
||||||
|
background: #FFFFFF;
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
.search-modes {
|
.search-modes {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
|
@ -152,6 +176,16 @@
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-item.selected {
|
||||||
|
outline: 3px solid #1C1C1E;
|
||||||
|
outline-offset: -3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .result-item.selected {
|
||||||
|
outline-color: #FFFFFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
.result-item img {
|
.result-item img {
|
||||||
|
|
@ -165,6 +199,56 @@
|
||||||
transform: scale(1.05);
|
transform: scale(1.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.selection-checkbox {
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
right: 8px;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #1C1C1E;
|
||||||
|
color: white;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .selection-checkbox {
|
||||||
|
background: #FFFFFF;
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-selected-bar {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 80px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: 12px 16px;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border-top: 1px solid var(--divider-color);
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-selected-btn {
|
||||||
|
width: 100%;
|
||||||
|
padding: 14px;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: #1C1C1E;
|
||||||
|
color: white;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .send-selected-btn {
|
||||||
|
background: #FFFFFF;
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
.result-overlay {
|
.result-overlay {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
|
|
@ -215,16 +299,27 @@
|
||||||
background: rgba(0, 0, 0, 0.5);
|
background: rgba(0, 0, 0, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.viewer-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.viewer-btn {
|
.viewer-btn {
|
||||||
width: 44px;
|
width: 44px;
|
||||||
height: 44px;
|
height: 44px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: rgba(255, 255, 255, 0.1);
|
background: rgba(255, 255, 255, 0.15);
|
||||||
color: white;
|
color: white;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(10px);
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewer-btn svg {
|
||||||
|
stroke: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.viewer-counter {
|
.viewer-counter {
|
||||||
|
|
@ -239,12 +334,38 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
touch-action: pan-y pinch-zoom;
|
||||||
}
|
}
|
||||||
|
|
||||||
.viewer-content img {
|
.viewer-content img {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.swipe-hint {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
background: rgba(0, 0, 0, 0.7);
|
||||||
|
border-radius: 20px;
|
||||||
|
color: white;
|
||||||
|
font-size: 13px;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
animation: fadeIn 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.swipe-hint svg {
|
||||||
|
stroke: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.viewer-nav {
|
.viewer-nav {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect, useRef } from 'react'
|
||||||
import { Search as SearchIcon, ChevronLeft, ChevronRight, Download, X } from 'lucide-react'
|
import { Search as SearchIcon, ChevronLeft, ChevronRight, Download, X, Plus } from 'lucide-react'
|
||||||
import { searchFurry, searchAnime, getFurryTags, getAnimeTags } from '../utils/api'
|
import { searchFurry, searchAnime, getFurryTags, getAnimeTags } from '../utils/api'
|
||||||
import { hapticFeedback } from '../utils/telegram'
|
import { hapticFeedback, getTelegramUser } from '../utils/telegram'
|
||||||
|
import CreatePostModal from '../components/CreatePostModal'
|
||||||
|
import api from '../utils/api'
|
||||||
import './Search.css'
|
import './Search.css'
|
||||||
|
|
||||||
export default function Search({ user }) {
|
export default function Search({ user }) {
|
||||||
|
|
@ -12,6 +14,12 @@ export default function Search({ user }) {
|
||||||
const [tagSuggestions, setTagSuggestions] = useState([])
|
const [tagSuggestions, setTagSuggestions] = useState([])
|
||||||
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 [selectionMode, setSelectionMode] = useState(false)
|
||||||
|
const [showCreatePost, setShowCreatePost] = useState(false)
|
||||||
|
const [imageForPost, setImageForPost] = useState(null)
|
||||||
|
const touchStartX = useRef(0)
|
||||||
|
const touchEndX = useRef(0)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (query.length > 1) {
|
if (query.length > 1) {
|
||||||
|
|
@ -94,11 +102,71 @@ export default function Search({ user }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const openViewer = (index) => {
|
const openViewer = (index) => {
|
||||||
setCurrentIndex(index)
|
if (selectionMode) {
|
||||||
setShowViewer(true)
|
toggleImageSelection(index)
|
||||||
|
} else {
|
||||||
|
setCurrentIndex(index)
|
||||||
|
setShowViewer(true)
|
||||||
|
hapticFeedback('light')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleImageSelection = (index) => {
|
||||||
|
const imageId = `${results[index].source}-${results[index].id}`
|
||||||
|
|
||||||
|
if (selectedImages.includes(imageId)) {
|
||||||
|
setSelectedImages(selectedImages.filter(id => id !== imageId))
|
||||||
|
} else {
|
||||||
|
setSelectedImages([...selectedImages, imageId])
|
||||||
|
}
|
||||||
hapticFeedback('light')
|
hapticFeedback('light')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const toggleSelectionMode = () => {
|
||||||
|
setSelectionMode(!selectionMode)
|
||||||
|
setSelectedImages([])
|
||||||
|
hapticFeedback('light')
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSendSelected = async () => {
|
||||||
|
if (selectedImages.length === 0) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
hapticFeedback('light')
|
||||||
|
|
||||||
|
const telegramUser = getTelegramUser()
|
||||||
|
|
||||||
|
if (telegramUser) {
|
||||||
|
// Найти выбранные изображения
|
||||||
|
const selectedPhotos = results.filter((img, index) => {
|
||||||
|
const imageId = `${img.source}-${img.id}`
|
||||||
|
return selectedImages.includes(imageId)
|
||||||
|
})
|
||||||
|
|
||||||
|
const photos = selectedPhotos.map(img => ({
|
||||||
|
url: img.url,
|
||||||
|
caption: `${img.source} - ${img.id}`
|
||||||
|
}))
|
||||||
|
|
||||||
|
await api.post('/bot/send-photos', {
|
||||||
|
userId: telegramUser.id,
|
||||||
|
photos: photos
|
||||||
|
})
|
||||||
|
|
||||||
|
hapticFeedback('success')
|
||||||
|
alert(`✅ ${selectedImages.length} изображений отправлено в ваш Telegram!`)
|
||||||
|
setSelectedImages([])
|
||||||
|
setSelectionMode(false)
|
||||||
|
} else {
|
||||||
|
alert('Функция доступна только в Telegram')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка:', error)
|
||||||
|
hapticFeedback('error')
|
||||||
|
alert('Ошибка отправки')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleNext = () => {
|
const handleNext = () => {
|
||||||
if (currentIndex < results.length - 1) {
|
if (currentIndex < results.length - 1) {
|
||||||
setCurrentIndex(currentIndex + 1)
|
setCurrentIndex(currentIndex + 1)
|
||||||
|
|
@ -113,34 +181,116 @@ export default function Search({ user }) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleTouchStart = (e) => {
|
||||||
|
touchStartX.current = e.touches[0].clientX
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleTouchMove = (e) => {
|
||||||
|
touchEndX.current = e.touches[0].clientX
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleTouchEnd = () => {
|
||||||
|
const diff = touchStartX.current - touchEndX.current
|
||||||
|
const threshold = 50 // минимальное расстояние для свайпа
|
||||||
|
|
||||||
|
if (Math.abs(diff) > threshold) {
|
||||||
|
if (diff > 0) {
|
||||||
|
// Свайп влево - следующая картинка
|
||||||
|
handleNext()
|
||||||
|
} else {
|
||||||
|
// Свайп вправо - предыдущая картинка
|
||||||
|
handlePrev()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleKeyDown = (e) => {
|
||||||
|
if (e.key === 'ArrowLeft') {
|
||||||
|
handlePrev()
|
||||||
|
} else if (e.key === 'ArrowRight') {
|
||||||
|
handleNext()
|
||||||
|
} else if (e.key === 'Escape') {
|
||||||
|
setShowViewer(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (showViewer) {
|
||||||
|
window.addEventListener('keydown', handleKeyDown)
|
||||||
|
return () => window.removeEventListener('keydown', handleKeyDown)
|
||||||
|
}
|
||||||
|
}, [showViewer, currentIndex])
|
||||||
|
|
||||||
const handleDownload = async () => {
|
const handleDownload = async () => {
|
||||||
const currentImage = results[currentIndex]
|
const currentImage = results[currentIndex]
|
||||||
if (!currentImage) return
|
if (!currentImage) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
hapticFeedback('light')
|
hapticFeedback('light')
|
||||||
const response = await fetch(currentImage.url)
|
|
||||||
const blob = await response.blob()
|
const telegramUser = getTelegramUser()
|
||||||
const url = window.URL.createObjectURL(blob)
|
|
||||||
const a = document.createElement('a')
|
if (telegramUser) {
|
||||||
a.href = url
|
// Отправить через backend в ЛС с ботом
|
||||||
a.download = `nakama-${currentImage.id}.jpg`
|
const caption = `${currentImage.source} - ID: ${currentImage.id}\nТеги: ${currentImage.tags.slice(0, 3).join(', ')}`
|
||||||
document.body.appendChild(a)
|
|
||||||
a.click()
|
await api.post('/bot/send-photo', {
|
||||||
document.body.removeChild(a)
|
userId: telegramUser.id,
|
||||||
window.URL.revokeObjectURL(url)
|
photoUrl: currentImage.url,
|
||||||
hapticFeedback('success')
|
caption: caption
|
||||||
|
})
|
||||||
|
|
||||||
|
hapticFeedback('success')
|
||||||
|
alert('✅ Изображение отправлено в ваш Telegram!')
|
||||||
|
} else {
|
||||||
|
// Fallback - обычное скачивание
|
||||||
|
const response = await fetch(currentImage.url)
|
||||||
|
const blob = await response.blob()
|
||||||
|
const url = window.URL.createObjectURL(blob)
|
||||||
|
const a = document.createElement('a')
|
||||||
|
a.href = url
|
||||||
|
a.download = `nakama-${currentImage.id}.jpg`
|
||||||
|
document.body.appendChild(a)
|
||||||
|
a.click()
|
||||||
|
document.body.removeChild(a)
|
||||||
|
window.URL.revokeObjectURL(url)
|
||||||
|
hapticFeedback('success')
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Ошибка скачивания:', error)
|
console.error('Ошибка:', error)
|
||||||
hapticFeedback('error')
|
hapticFeedback('error')
|
||||||
|
alert('Ошибка отправки. Проверьте настройки бота.')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleCreatePost = () => {
|
||||||
|
const currentImage = results[currentIndex]
|
||||||
|
setImageForPost(currentImage.url)
|
||||||
|
setShowViewer(false)
|
||||||
|
setShowCreatePost(true)
|
||||||
|
hapticFeedback('light')
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlePostCreated = (newPost) => {
|
||||||
|
setShowCreatePost(false)
|
||||||
|
setImageForPost(null)
|
||||||
|
hapticFeedback('success')
|
||||||
|
alert('✅ Пост создан!')
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="search-page">
|
<div className="search-page">
|
||||||
{/* Хедер */}
|
{/* Хедер */}
|
||||||
<div className="search-header">
|
<div className="search-header">
|
||||||
<h1>Поиск</h1>
|
<h1>Поиск</h1>
|
||||||
|
{results.length > 0 && (
|
||||||
|
<button
|
||||||
|
className={`selection-toggle ${selectionMode ? 'active' : ''}`}
|
||||||
|
onClick={toggleSelectionMode}
|
||||||
|
>
|
||||||
|
{selectionMode ? 'Отмена' : 'Выбрать'}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Режимы поиска */}
|
{/* Режимы поиска */}
|
||||||
|
|
@ -222,26 +372,45 @@ export default function Search({ user }) {
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="results-grid">
|
<div className="results-grid">
|
||||||
{results.map((item, index) => (
|
{results.map((item, index) => {
|
||||||
<div
|
const imageId = `${item.source}-${item.id}`
|
||||||
key={`${item.source}-${item.id}`}
|
const isSelected = selectedImages.includes(imageId)
|
||||||
className="result-item card"
|
|
||||||
onClick={() => openViewer(index)}
|
return (
|
||||||
>
|
<div
|
||||||
<img src={item.preview} alt={`Result ${index}`} />
|
key={imageId}
|
||||||
<div className="result-overlay">
|
className={`result-item card ${isSelected ? 'selected' : ''}`}
|
||||||
<span className="result-source">{item.source}</span>
|
onClick={() => openViewer(index)}
|
||||||
<span className="result-rating">{item.rating}</span>
|
>
|
||||||
|
<img src={item.preview} alt={`Result ${index}`} />
|
||||||
|
<div className="result-overlay">
|
||||||
|
<span className="result-source">{item.source}</span>
|
||||||
|
<span className="result-rating">{item.rating}</span>
|
||||||
|
</div>
|
||||||
|
{selectionMode && (
|
||||||
|
<div className="selection-checkbox">
|
||||||
|
{isSelected && <span>✓</span>}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)
|
||||||
))}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Кнопка отправки выбранных */}
|
||||||
|
{selectionMode && selectedImages.length > 0 && (
|
||||||
|
<div className="send-selected-bar">
|
||||||
|
<button className="send-selected-btn" onClick={handleSendSelected}>
|
||||||
|
Отправить в Telegram ({selectedImages.length})
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Просмотрщик изображений */}
|
{/* Просмотрщик изображений */}
|
||||||
{showViewer && results[currentIndex] && (
|
{showViewer && results[currentIndex] && (
|
||||||
<div className="image-viewer" onClick={() => setShowViewer(false)}>
|
<div className="image-viewer">
|
||||||
<div className="viewer-header">
|
<div className="viewer-header">
|
||||||
<button className="viewer-btn" onClick={() => setShowViewer(false)}>
|
<button className="viewer-btn" onClick={() => setShowViewer(false)}>
|
||||||
<X size={24} />
|
<X size={24} />
|
||||||
|
|
@ -249,27 +418,50 @@ export default function Search({ user }) {
|
||||||
<span className="viewer-counter">
|
<span className="viewer-counter">
|
||||||
{currentIndex + 1} / {results.length}
|
{currentIndex + 1} / {results.length}
|
||||||
</span>
|
</span>
|
||||||
<button className="viewer-btn" onClick={(e) => { e.stopPropagation(); handleDownload(); }}>
|
<div className="viewer-actions">
|
||||||
<Download size={24} />
|
<button className="viewer-btn" onClick={handleCreatePost} title="Создать пост">
|
||||||
</button>
|
<Plus size={24} />
|
||||||
|
</button>
|
||||||
|
<button className="viewer-btn" onClick={handleDownload} title="Отправить в ЛС">
|
||||||
|
<Download size={24} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="viewer-content" onClick={e => e.stopPropagation()}>
|
<div
|
||||||
<img src={results[currentIndex].url} alt="Full view" />
|
className="viewer-content"
|
||||||
|
onTouchStart={handleTouchStart}
|
||||||
|
onTouchMove={handleTouchMove}
|
||||||
|
onTouchEnd={handleTouchEnd}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={results[currentIndex].url}
|
||||||
|
alt="Full view"
|
||||||
|
draggable={false}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Индикатор свайпа */}
|
||||||
|
<div className="swipe-hint">
|
||||||
|
<ChevronLeft size={20} style={{ opacity: currentIndex > 0 ? 1 : 0.3 }} />
|
||||||
|
<span>Свайпайте для переключения</span>
|
||||||
|
<ChevronRight size={20} style={{ opacity: currentIndex < results.length - 1 ? 1 : 0.3 }} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="viewer-nav">
|
<div className="viewer-nav">
|
||||||
<button
|
<button
|
||||||
className="nav-btn"
|
className="nav-btn"
|
||||||
onClick={(e) => { e.stopPropagation(); handlePrev(); }}
|
onClick={handlePrev}
|
||||||
disabled={currentIndex === 0}
|
disabled={currentIndex === 0}
|
||||||
|
style={{ opacity: currentIndex === 0 ? 0.3 : 1 }}
|
||||||
>
|
>
|
||||||
<ChevronLeft size={32} />
|
<ChevronLeft size={32} />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="nav-btn"
|
className="nav-btn"
|
||||||
onClick={(e) => { e.stopPropagation(); handleNext(); }}
|
onClick={handleNext}
|
||||||
disabled={currentIndex === results.length - 1}
|
disabled={currentIndex === results.length - 1}
|
||||||
|
style={{ opacity: currentIndex === results.length - 1 ? 0.3 : 1 }}
|
||||||
>
|
>
|
||||||
<ChevronRight size={32} />
|
<ChevronRight size={32} />
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
╔═══════════════════════════════════════════════════════════════════════╗
|
|
||||||
║ ║
|
|
||||||
║ ⚡ ОБНОВЛЕНИЕ v2.1.3 - СКОПИРУЙ И ЗАПУСТИ ⚡ ║
|
|
||||||
║ ║
|
|
||||||
╚═══════════════════════════════════════════════════════════════════════╝
|
|
||||||
|
|
||||||
|
|
||||||
✅ ЧТО ИСПРАВЛЕНО:
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
✅ Комментарии НЕ прыгают (на любом устройстве)
|
|
||||||
✅ Поле ввода полностью активно
|
|
||||||
✅ Ошибки 401 исправлены
|
|
||||||
✅ Кнопки видны в тёмной теме (белые с чёрным текстом)
|
|
||||||
|
|
||||||
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
📤 КОМАНДЫ ДЛЯ ОБНОВЛЕНИЯ
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
|
|
||||||
1️⃣ НА КОМПЬЮТЕРЕ (Terminal):
|
|
||||||
────────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
cd /Users/glpshchn/Desktop/nakama
|
|
||||||
|
|
||||||
scp frontend/index.html root@ваш_IP:/var/www/nakama/frontend/
|
|
||||||
scp frontend/src/components/CommentsModal.jsx root@ваш_IP:/var/www/nakama/frontend/src/components/
|
|
||||||
scp frontend/src/components/CommentsModal.css root@ваш_IP:/var/www/nakama/frontend/src/components/
|
|
||||||
scp frontend/src/pages/Feed.css root@ваш_IP:/var/www/nakama/frontend/src/pages/
|
|
||||||
scp frontend/src/pages/Search.css root@ваш_IP:/var/www/nakama/frontend/src/pages/
|
|
||||||
scp backend/middleware/auth.js root@ваш_IP:/var/www/nakama/backend/middleware/
|
|
||||||
|
|
||||||
|
|
||||||
2️⃣ НА СЕРВЕРЕ (скопируйте весь блок):
|
|
||||||
────────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
ssh root@ваш_IP
|
|
||||||
cd /var/www/nakama/frontend && npm run build && cd .. && pm2 restart nakama-backend && pm2 logs nakama-backend --lines 20
|
|
||||||
|
|
||||||
|
|
||||||
✅ ГОТОВО! Проверяйте: https://nakama.glpshchn.ru
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
|
|
||||||
🧪 ПРОВЕРЬТЕ:
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
□ Комментарии:
|
|
||||||
1. Откройте любой пост
|
|
||||||
2. Нажмите на иконку 💬
|
|
||||||
3. Нажмите на поле ввода
|
|
||||||
4. Окно НЕ должно прыгать ✅
|
|
||||||
5. Введите текст
|
|
||||||
6. Отправьте комментарий ✅
|
|
||||||
|
|
||||||
□ Тёмная тема:
|
|
||||||
1. Профиль → Тема → Тёмная
|
|
||||||
2. Вернитесь на главную
|
|
||||||
3. Кнопки "Все", "Furry" и т.д. - БЕЛЫЕ ✅
|
|
||||||
4. Текст на кнопках ЧЁРНЫЙ ✅
|
|
||||||
|
|
||||||
□ Логи (на сервере):
|
|
||||||
1. pm2 logs nakama-backend
|
|
||||||
2. НЕ должно быть ошибок 401 ✅
|
|
||||||
|
|
||||||
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
📋 ИЗМЕНЕНИЯ:
|
|
||||||
|
|
||||||
Frontend:
|
|
||||||
• index.html - viewport fix
|
|
||||||
• CommentsModal.jsx - правильный onClick
|
|
||||||
• CommentsModal.css - предотвращение прыжков
|
|
||||||
• Feed.css - белые кнопки в тёмной теме
|
|
||||||
• Search.css - белые кнопки в тёмной теме
|
|
||||||
|
|
||||||
Backend:
|
|
||||||
• auth.js - смягчена проверка авторизации
|
|
||||||
|
|
||||||
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
💡 ЕСЛИ ЧТО-ТО НЕ РАБОТАЕТ:
|
|
||||||
|
|
||||||
pm2 restart nakama-backend
|
|
||||||
sudo systemctl restart nginx
|
|
||||||
pm2 logs nakama-backend
|
|
||||||
|
|
||||||
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
🎉 v2.1.3 готов!
|
|
||||||
|
|
||||||
Все критические баги исправлены.
|
|
||||||
Приложение стабильно работает на nakama.glpshchn.ru
|
|
||||||
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
╔═══════════════════════════════════════════════════════════════════════╗
|
|
||||||
║ ║
|
|
||||||
║ ⚡ ПРОСТОЕ РЕШЕНИЕ - МОДАЛКА НА ВЕСЬ ЭКРАН ⚡ ║
|
|
||||||
║ ║
|
|
||||||
╚═══════════════════════════════════════════════════════════════════════╝
|
|
||||||
|
|
||||||
|
|
||||||
РЕШЕНИЕ:
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
Комментарии открываются НА ВЕСЬ ЭКРАН (как отдельная страница)
|
|
||||||
|
|
||||||
✓ Кнопка X вверху слева - закрывает
|
|
||||||
✓ Поле ввода внизу (над навигацией) - РАБОТАЕТ
|
|
||||||
✓ Ничего НЕ прыгает
|
|
||||||
✓ Всё кликабельно
|
|
||||||
|
|
||||||
|
|
||||||
ОБНОВИТЬ (2 файла):
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
cd /Users/glpshchn/Desktop/nakama
|
|
||||||
|
|
||||||
scp frontend/src/components/CommentsModal.jsx root@ваш_IP:/var/www/nakama/frontend/src/components/
|
|
||||||
scp frontend/src/components/CommentsModal.css root@ваш_IP:/var/www/nakama/frontend/src/components/
|
|
||||||
|
|
||||||
|
|
||||||
ssh root@ваш_IP
|
|
||||||
cd /var/www/nakama/frontend && npm run build
|
|
||||||
|
|
||||||
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
✅ РАБОТАЕТ:
|
|
||||||
|
|
||||||
□ Откройте пост
|
|
||||||
□ Нажмите 💬
|
|
||||||
□ Откроется на весь экран
|
|
||||||
□ Нажмите на поле ввода ✅
|
|
||||||
□ Напишите комментарий ✅
|
|
||||||
□ Отправьте ✅
|
|
||||||
□ Нажмите X - закроется ✅
|
|
||||||
|
|
||||||
|
|
||||||
Время: 1 минута
|
|
||||||
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
╔═══════════════════════════════════════════════════════════════════════╗
|
|
||||||
║ ║
|
|
||||||
║ ⚫ МОНОХРОМНАЯ ПАЛИТРА - БЕЗ СИНЕГО ⚫ ║
|
|
||||||
║ ║
|
|
||||||
╚═══════════════════════════════════════════════════════════════════════╝
|
|
||||||
|
|
||||||
|
|
||||||
🎨 ЦВЕТОВАЯ СХЕМА:
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
СВЕТЛАЯ ТЕМА:
|
|
||||||
Неактивная кнопка: ░░░ Светло-серая (#E5E5EA)
|
|
||||||
Активная кнопка: ███ Чёрная (#1C1C1E)
|
|
||||||
Кнопка +: ███ Чёрная
|
|
||||||
Кнопка отправить: ███ Чёрная
|
|
||||||
|
|
||||||
ТЁМНАЯ ТЕМА:
|
|
||||||
Неактивная кнопка: ▓▓▓ Тёмно-серая (#3A3A3C)
|
|
||||||
Активная кнопка: ▓▓▓ Белая (#FFFFFF)
|
|
||||||
Кнопка +: ▓▓▓ Белая
|
|
||||||
Кнопка отправить: ▓▓▓ Белая
|
|
||||||
|
|
||||||
|
|
||||||
ОБНОВЛЕНИЕ (5 файлов):
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
cd /Users/glpshchn/Desktop/nakama
|
|
||||||
|
|
||||||
scp frontend/src/pages/Feed.css root@ваш_IP:/var/www/nakama/frontend/src/pages/
|
|
||||||
scp frontend/src/pages/Search.css root@ваш_IP:/var/www/nakama/frontend/src/pages/
|
|
||||||
scp frontend/src/components/CommentsModal.css root@ваш_IP:/var/www/nakama/frontend/src/components/
|
|
||||||
scp frontend/src/components/PostMenu.css root@ваш_IP:/var/www/nakama/frontend/src/components/
|
|
||||||
scp frontend/src/components/CreatePostModal.css root@ваш_IP:/var/www/nakama/frontend/src/components/
|
|
||||||
|
|
||||||
|
|
||||||
ssh root@ваш_IP "cd /var/www/nakama/frontend && npm run build"
|
|
||||||
|
|
||||||
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
✅ РЕЗУЛЬТАТ:
|
|
||||||
|
|
||||||
Светлая тема:
|
|
||||||
[░░░ Furry ░░░] [███ Все ███] [░░░ Anime ░░░]
|
|
||||||
неактивные активная
|
|
||||||
|
|
||||||
Тёмная тема:
|
|
||||||
[▓▓▓ Furry ▓▓▓] [▓▓▓ Все ▓▓▓] [▓▓▓ Anime ▓▓▓]
|
|
||||||
неактивные БЕЛАЯ
|
|
||||||
|
|
||||||
|
|
||||||
НЕТ СИНЕГО НИГДЕ!
|
|
||||||
Только чёрное и белое! ⚫⚪
|
|
||||||
|
|
||||||
109
✅_READY.txt
109
✅_READY.txt
|
|
@ -1,109 +0,0 @@
|
||||||
╔═══════════════════════════════════════════════════════════════════════╗
|
|
||||||
║ ║
|
|
||||||
║ ✅ NakamaSpace v2.1.1 - ГОТОВ К ДЕПЛОЮ! ✅ ║
|
|
||||||
║ ║
|
|
||||||
║ nakama.glpshchn.ru ║
|
|
||||||
║ ║
|
|
||||||
╚═══════════════════════════════════════════════════════════════════════╝
|
|
||||||
|
|
||||||
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
✨ ВСЕ ИСПРАВЛЕНИЯ ПРИМЕНЕНЫ
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
✅ Окно комментариев работает идеально
|
|
||||||
✅ Репосты удалены полностью
|
|
||||||
✅ Тёмная тема - всё видно
|
|
||||||
✅ Фильтр NSFW работает
|
|
||||||
✅ Профиль упрощён
|
|
||||||
|
|
||||||
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
🚀 ЗАГРУЗИТЬ НА СЕРВЕР
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
Выполните 3 команды:
|
|
||||||
|
|
||||||
|
|
||||||
┌─ 1️⃣ НА КОМПЬЮТЕРЕ ──────────────────────────────────────────────────┐
|
|
||||||
|
|
||||||
cd /Users/glpshchn/Desktop
|
|
||||||
tar -czf nakama.tar.gz nakama --exclude='node_modules' --exclude='dist'
|
|
||||||
scp nakama.tar.gz root@ваш_IP:/tmp/
|
|
||||||
|
|
||||||
└─────────────────────────────────────────────────────────────────────┘
|
|
||||||
|
|
||||||
|
|
||||||
┌─ 2️⃣ ПОДКЛЮЧИТЬСЯ К СЕРВЕРУ ─────────────────────────────────────────┐
|
|
||||||
|
|
||||||
ssh root@ваш_IP
|
|
||||||
|
|
||||||
└─────────────────────────────────────────────────────────────────────┘
|
|
||||||
|
|
||||||
|
|
||||||
┌─ 3️⃣ НА СЕРВЕРЕ (вся команда одной строкой) ─────────────────────────┐
|
|
||||||
|
|
||||||
cd /var/www/nakama && cp .env /tmp/e && cp -r backend/uploads /tmp/u && cd /var/www && sudo rm -rf nakama && sudo tar -xzf /tmp/nakama.tar.gz && cd nakama && cp /tmp/e .env && mkdir -p backend/uploads && cp -r /tmp/u/* backend/uploads/ && chmod +x update-server.sh && ./update-server.sh
|
|
||||||
|
|
||||||
└─────────────────────────────────────────────────────────────────────┘
|
|
||||||
|
|
||||||
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
✅ ПРОВЕРКА
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
На сервере:
|
|
||||||
|
|
||||||
pm2 status
|
|
||||||
pm2 logs nakama-backend
|
|
||||||
curl https://nakama.glpshchn.ru/health
|
|
||||||
|
|
||||||
|
|
||||||
В браузере:
|
|
||||||
|
|
||||||
https://nakama.glpshchn.ru
|
|
||||||
|
|
||||||
|
|
||||||
В Telegram:
|
|
||||||
|
|
||||||
Откройте бота → Menu Button
|
|
||||||
|
|
||||||
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
🎯 ЧТО ПРОВЕРИТЬ
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
□ Откройте пост → нажмите 💬
|
|
||||||
└─ Окно не на весь экран
|
|
||||||
└─ Поле ввода активно
|
|
||||||
└─ Можно написать комментарий
|
|
||||||
|
|
||||||
□ Только 2 кнопки под постом
|
|
||||||
└─ ❤️ Лайк
|
|
||||||
└─ 💬 Комментарий
|
|
||||||
|
|
||||||
□ Переключите тёмную тему
|
|
||||||
└─ Все иконки видны
|
|
||||||
└─ Текст читаем
|
|
||||||
|
|
||||||
□ Профиль → Фильтр NSFW
|
|
||||||
└─ Переключается
|
|
||||||
└─ Посты появляются/исчезают
|
|
||||||
|
|
||||||
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
📚 ЕСЛИ НУЖНА ПОМОЩЬ:
|
|
||||||
|
|
||||||
README_DEPLOY.txt - Простая инструкция
|
|
||||||
CHANGELOG_v2.1.1.md - Что изменилось
|
|
||||||
UPLOAD_TO_SERVER.md - Подробно
|
|
||||||
DEPLOYMENT.md - Полный гайд
|
|
||||||
|
|
||||||
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
🎉 Готово! Все проблемы исправлены!
|
|
||||||
|
|
||||||
Осталось только загрузить на сервер (3 команды выше) ⬆️
|
|
||||||
|
|
||||||
|
|
@ -1,90 +0,0 @@
|
||||||
╔═══════════════════════════════════════════════════════════════════════╗
|
|
||||||
║ ║
|
|
||||||
║ ✅ ИДЕАЛЬНОЕ РЕШЕНИЕ v2.1.3 ✅ ║
|
|
||||||
║ ║
|
|
||||||
╚═══════════════════════════════════════════════════════════════════════╝
|
|
||||||
|
|
||||||
|
|
||||||
🎯 РЕШЕНИЕ:
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
1. Комментарии НЕ прыгают:
|
|
||||||
✓ height: 60dvh (не меняется при клавиатуре)
|
|
||||||
✓ Telegram WebApp API viewportChanged event
|
|
||||||
✓ position: fixed
|
|
||||||
✓ Правильный onClick handler
|
|
||||||
|
|
||||||
2. Кнопки в тёмной теме:
|
|
||||||
✓ ВСЕ кнопки БЕЛЫЕ (#FFFFFF)
|
|
||||||
✓ Текст чёрный (#000000)
|
|
||||||
✓ Активная: БЕЛАЯ с синей рамкой + синий текст
|
|
||||||
|
|
||||||
|
|
||||||
ОБНОВЛЕНИЕ (4 файла):
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
НА КОМПЬЮТЕРЕ:
|
|
||||||
────────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
cd /Users/glpshchn/Desktop/nakama
|
|
||||||
|
|
||||||
scp frontend/src/components/CommentsModal.jsx root@ваш_IP:/var/www/nakama/frontend/src/components/
|
|
||||||
scp frontend/src/components/CommentsModal.css root@ваш_IP:/var/www/nakama/frontend/src/components/
|
|
||||||
scp frontend/src/pages/Feed.css root@ваш_IP:/var/www/nakama/frontend/src/pages/
|
|
||||||
scp frontend/src/pages/Search.css root@ваш_IP:/var/www/nakama/frontend/src/pages/
|
|
||||||
|
|
||||||
|
|
||||||
НА СЕРВЕРЕ:
|
|
||||||
────────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
ssh root@ваш_IP
|
|
||||||
cd /var/www/nakama/frontend
|
|
||||||
npm run build
|
|
||||||
|
|
||||||
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
✅ РЕЗУЛЬТАТ В ТЁМНОЙ ТЕМЕ:
|
|
||||||
|
|
||||||
Кнопки на главной:
|
|
||||||
┌─────┐ ┌─────┐ ┌───────┐ ┌───────┐
|
|
||||||
│ Все │ │Furry│ │ Anime │ │ Other │ ← ВСЕ БЕЛЫЕ
|
|
||||||
└─────┘ └─────┘ └───────┘ └───────┘
|
|
||||||
▲
|
|
||||||
│
|
|
||||||
└─ Активная: белая с СИНЕЙ РАМКОЙ
|
|
||||||
|
|
||||||
Кнопки в поиске:
|
|
||||||
┌─────┐ ┌───────┐ ┌───────┐
|
|
||||||
│Furry│ │ Anime │ │ Mixed │ ← ВСЕ БЕЛЫЕ
|
|
||||||
└─────┘ └───────┘ └───────┘
|
|
||||||
|
|
||||||
|
|
||||||
Комментарии:
|
|
||||||
• Окно фиксированное (60dvh)
|
|
||||||
• НЕ прыгает при фокусе
|
|
||||||
• Поле ввода активно
|
|
||||||
• Работает на телефоне и десктопе
|
|
||||||
|
|
||||||
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
🧪 ПРОВЕРКА:
|
|
||||||
|
|
||||||
1. Откройте https://nakama.glpshchn.ru
|
|
||||||
2. Переключите тёмную тему
|
|
||||||
3. Главная → кнопки "Все", "Furry" и т.д. - БЕЛЫЕ ✅
|
|
||||||
4. Откройте пост → комментарии 💬
|
|
||||||
5. Нажмите на поле ввода
|
|
||||||
6. Окно НЕ прыгает ✅
|
|
||||||
7. Введите комментарий ✅
|
|
||||||
|
|
||||||
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
🎉 ИДЕАЛЬНО!
|
|
||||||
|
|
||||||
Время обновления: 2 минуты
|
|
||||||
Изменено: 4 файла (только frontend)
|
|
||||||
Backend перезапускать НЕ нужно
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,128 @@
|
||||||
|
╔═══════════════════════════════════════════════════════════════════════╗
|
||||||
|
║ ║
|
||||||
|
║ ✨ ВСЕ ФУНКЦИИ РЕАЛИЗОВАНЫ И ГОТОВЫ ✨ ║
|
||||||
|
║ v2.2.0 - Major Update ║
|
||||||
|
║ ║
|
||||||
|
╚═══════════════════════════════════════════════════════════════════════╝
|
||||||
|
|
||||||
|
|
||||||
|
🎉 ЧТО ДОБАВЛЕНО:
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
|
||||||
|
1. До 5 картинок в одном посте
|
||||||
|
✓ Множественная загрузка файлов
|
||||||
|
✓ Сетка превью в создании поста
|
||||||
|
✓ Карусель в ленте (свайп между картинками)
|
||||||
|
✓ Точки-индикаторы внизу
|
||||||
|
|
||||||
|
2. Создать пост из поиска (репост)
|
||||||
|
✓ Кнопка "+" в просмотрщике
|
||||||
|
✓ Картинка автоматически добавляется
|
||||||
|
✓ Можно добавить текст и теги
|
||||||
|
|
||||||
|
3. Отправка в ЛС с ботом
|
||||||
|
✓ Одна картинка из просмотрщика
|
||||||
|
✓ Несколько картинок (режим выбора)
|
||||||
|
✓ До 50 фото за раз
|
||||||
|
✓ Media Group в Telegram
|
||||||
|
|
||||||
|
4. Swipe перелистывание
|
||||||
|
✓ В просмотрщике поиска
|
||||||
|
✓ В карусели поста
|
||||||
|
✓ Стрелки на клавиатуре
|
||||||
|
|
||||||
|
5. Монохромный дизайн
|
||||||
|
✓ Только чёрное и белое
|
||||||
|
✓ Без синих кнопок
|
||||||
|
|
||||||
|
|
||||||
|
ОБНОВЛЕНИЕ (13 файлов):
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
|
||||||
|
Backend (5):
|
||||||
|
scp backend/models/Post.js root@ваш_IP:/var/www/nakama/backend/models/
|
||||||
|
scp backend/routes/posts.js root@ваш_IP:/var/www/nakama/backend/routes/
|
||||||
|
scp backend/bot.js root@ваш_IP:/var/www/nakama/backend/
|
||||||
|
scp backend/routes/bot.js root@ваш_IP:/var/www/nakama/backend/routes/
|
||||||
|
scp backend/server.js root@ваш_IP:/var/www/nakama/backend/
|
||||||
|
|
||||||
|
Frontend (8):
|
||||||
|
scp frontend/src/components/CreatePostModal.jsx root@ваш_IP:/var/www/nakama/frontend/src/components/
|
||||||
|
scp frontend/src/components/CreatePostModal.css root@ваш_IP:/var/www/nakama/frontend/src/components/
|
||||||
|
scp frontend/src/components/PostCard.jsx root@ваш_IP:/var/www/nakama/frontend/src/components/
|
||||||
|
scp frontend/src/components/PostCard.css root@ваш_IP:/var/www/nakama/frontend/src/components/
|
||||||
|
scp frontend/src/pages/Search.jsx root@ваш_IP:/var/www/nakama/frontend/src/pages/
|
||||||
|
scp frontend/src/pages/Search.css root@ваш_IP:/var/www/nakama/frontend/src/pages/
|
||||||
|
scp frontend/src/components/Navigation.css root@ваш_IP:/var/www/nakama/frontend/src/components/
|
||||||
|
scp frontend/src/components/CommentsModal.css root@ваш_IP:/var/www/nakama/frontend/src/components/
|
||||||
|
|
||||||
|
На сервере:
|
||||||
|
ssh root@ваш_IP
|
||||||
|
cd /var/www/nakama/frontend && npm run build && cd .. && pm2 restart nakama-backend
|
||||||
|
|
||||||
|
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
|
||||||
|
📱 КАК ИСПОЛЬЗОВАТЬ:
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
|
||||||
|
Создать пост с несколькими фото:
|
||||||
|
1. Лента → кнопка "+"
|
||||||
|
2. Нажмите иконку 🖼️
|
||||||
|
3. Выберите до 5 фото
|
||||||
|
4. Превью появится сеткой
|
||||||
|
5. Добавьте текст и теги
|
||||||
|
6. Опубликовать
|
||||||
|
|
||||||
|
Репост из поиска:
|
||||||
|
1. Поиск → найдите картинку
|
||||||
|
2. Откройте просмотрщик
|
||||||
|
3. Нажмите кнопку "+" вверху
|
||||||
|
4. Откроется создание поста с этой картинкой
|
||||||
|
5. Добавьте текст и теги
|
||||||
|
6. Опубликовать
|
||||||
|
|
||||||
|
Отправить в бота:
|
||||||
|
1. Поиск → просмотрщик
|
||||||
|
2. Кнопка "Download" → 1 фото в ЛС
|
||||||
|
|
||||||
|
ИЛИ:
|
||||||
|
|
||||||
|
1. Поиск → кнопка "Выбрать"
|
||||||
|
2. Тапайте по картинкам
|
||||||
|
3. "Отправить в Telegram (N)" → все в ЛС
|
||||||
|
|
||||||
|
Свайп в посте:
|
||||||
|
1. Если в посте несколько картинок
|
||||||
|
2. Свайпайте влево/вправо
|
||||||
|
3. Точки внизу показывают текущую
|
||||||
|
|
||||||
|
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
|
||||||
|
⚙️ НАСТРОЙКА БОТА:
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
|
||||||
|
В .env на сервере:
|
||||||
|
TELEGRAM_BOT_TOKEN=ваш_токен_от_BotFather
|
||||||
|
|
||||||
|
Пользователь должен написать /start боту один раз
|
||||||
|
|
||||||
|
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
|
||||||
|
✅ ГОТОВО:
|
||||||
|
|
||||||
|
✓ До 5 фото в посте
|
||||||
|
✓ Карусель в ленте
|
||||||
|
✓ Репост из поиска
|
||||||
|
✓ Отправка в бота
|
||||||
|
✓ Swipe навигация
|
||||||
|
✓ Монохромный дизайн
|
||||||
|
|
||||||
|
|
||||||
|
5 минут обновления
|
||||||
|
https://nakama.glpshchn.ru
|
||||||
|
|
||||||
|
🎉 NakamaSpace v2.2.0!
|
||||||
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
╔═══════════════════════════════════════════════════════════════════════╗
|
|
||||||
║ ║
|
|
||||||
║ ✨ КОММЕНТАРИИ С ПОСТОМ - ГОТОВО ✨ ║
|
|
||||||
║ ║
|
|
||||||
╚═══════════════════════════════════════════════════════════════════════╝
|
|
||||||
|
|
||||||
|
|
||||||
ЧТО ИЗМЕНИЛОСЬ:
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
Комментарии открываются на ВЕСЬ ЭКРАН и показывают:
|
|
||||||
|
|
||||||
┌──────────────────────────────────────┐
|
|
||||||
│ [X] Комментарии │ ← Хедер
|
|
||||||
├──────────────────────────────────────┤
|
|
||||||
│ 👤 Автор поста │
|
|
||||||
│ Текст поста... │
|
|
||||||
│ [Изображение если есть] │ ← Пост
|
|
||||||
├──────────────────────────────────────┤
|
|
||||||
│ 💬 Комментарий 1 │
|
|
||||||
│ 💬 Комментарий 2 │
|
|
||||||
│ ... │ ← Комментарии
|
|
||||||
│ │
|
|
||||||
├──────────────────────────────────────┤
|
|
||||||
│ [Написать комментарий...] [➤] │ ← Форма ввода
|
|
||||||
└──────────────────────────────────────┘
|
|
||||||
Навигация (Лента, Поиск и т.д.)
|
|
||||||
|
|
||||||
|
|
||||||
ОБНОВИТЬ:
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
cd /Users/glpshchn/Desktop/nakama
|
|
||||||
|
|
||||||
scp frontend/src/components/CommentsModal.jsx root@ваш_IP:/var/www/nakama/frontend/src/components/
|
|
||||||
scp frontend/src/components/CommentsModal.css root@ваш_IP:/var/www/nakama/frontend/src/components/
|
|
||||||
|
|
||||||
|
|
||||||
ssh root@ваш_IP
|
|
||||||
cd /var/www/nakama/frontend && npm run build
|
|
||||||
|
|
||||||
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
✅ РЕЗУЛЬТАТ:
|
|
||||||
|
|
||||||
✓ Пост виден вверху модалки
|
|
||||||
✓ Поле ввода внизу РАБОТАЕТ
|
|
||||||
✓ Ничего НЕ прыгает
|
|
||||||
✓ Кнопка X закрывает
|
|
||||||
|
|
||||||
|
|
||||||
1 минута
|
|
||||||
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
╔═══════════════════════════════════════════════════════════════════════╗
|
|
||||||
║ ⭐ НАЧНИТЕ ОТСЮДА ⭐ ║
|
|
||||||
║ NakamaSpace v2.1.1 - Production Ready ║
|
|
||||||
╚═══════════════════════════════════════════════════════════════════════╝
|
|
||||||
|
|
||||||
🎉 ВСЕ ИСПРАВЛЕНИЯ ПРИМЕНЕНЫ!
|
|
||||||
|
|
||||||
Окно комментариев ✅
|
|
||||||
Репосты удалены ✅
|
|
||||||
Тёмная тема видна ✅
|
|
||||||
Фильтры работают ✅
|
|
||||||
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
🚀 ЗАГРУЗИТЬ НА СЕРВЕР - 3 КОМАНДЫ:
|
|
||||||
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
📍 ШАГ 1 - НА КОМПЬЮТЕРЕ (Terminal):
|
|
||||||
|
|
||||||
cd /Users/glpshchn/Desktop
|
|
||||||
tar -czf nakama.tar.gz nakama --exclude='node_modules' --exclude='dist'
|
|
||||||
scp nakama.tar.gz root@ваш_IP:/tmp/
|
|
||||||
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
📍 ШАГ 2 - ПОДКЛЮЧИТЬСЯ К СЕРВЕРУ:
|
|
||||||
|
|
||||||
ssh root@ваш_IP
|
|
||||||
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
📍 ШАГ 3 - НА СЕРВЕРЕ (скопируйте весь блок):
|
|
||||||
|
|
||||||
cd /var/www/nakama && cp .env /tmp/env-backup && cp -r backend/uploads /tmp/uploads-backup && cd /var/www && sudo rm -rf nakama && sudo tar -xzf /tmp/nakama.tar.gz && cd nakama && cp /tmp/env-backup .env && mkdir -p backend/uploads && cp -r /tmp/uploads-backup/* backend/uploads/ && chmod +x update-server.sh && ./update-server.sh
|
|
||||||
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
✅ ГОТОВО!
|
|
||||||
|
|
||||||
Проверьте: https://nakama.glpshchn.ru
|
|
||||||
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
📖 ПОДРОБНАЯ ИНСТРУКЦИЯ:
|
|
||||||
|
|
||||||
README_DEPLOY.txt - Простая инструкция
|
|
||||||
UPLOAD_TO_SERVER.md - Детальная инструкция
|
|
||||||
CHANGELOG_v2.1.1.md - Что изменилось
|
|
||||||
DEPLOYMENT.md - Полный гайд по деплою
|
|
||||||
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
💡 ЕСЛИ ЧТО-ТО НЕ РАБОТАЕТ:
|
|
||||||
|
|
||||||
pm2 logs nakama-backend - Посмотреть логи
|
|
||||||
pm2 restart nakama-backend - Перезапустить
|
|
||||||
sudo systemctl restart nginx - Перезапустить Nginx
|
|
||||||
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
🎊 Успешного деплоя! 🚀
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
╔═══════════════════════════════════════════════════════════════════════╗
|
||||||
|
║ ║
|
||||||
|
║ 🎨 НЕСКОЛЬКО КАРТИНОК В ОДНОМ ПОСТЕ 🎨 ║
|
||||||
|
║ ║
|
||||||
|
╚═══════════════════════════════════════════════════════════════════════╝
|
||||||
|
|
||||||
|
|
||||||
|
✨ НОВЫЕ ФУНКЦИИ:
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
|
||||||
|
1. До 5 картинок в одном посте
|
||||||
|
• Множественная загрузка файлов
|
||||||
|
• Сетка превью
|
||||||
|
• Счётчик "N/5"
|
||||||
|
• Удаление каждой картинки отдельно
|
||||||
|
|
||||||
|
2. Создать пост из поиска
|
||||||
|
• Кнопка "+" в просмотрщике
|
||||||
|
• Картинка автоматически добавится в пост
|
||||||
|
• Можно добавить текст и теги
|
||||||
|
|
||||||
|
3. Комбинация загруженных и внешних
|
||||||
|
• Загрузить свои фото
|
||||||
|
• Добавить из поиска
|
||||||
|
• Всё вместе в одном посте
|
||||||
|
|
||||||
|
|
||||||
|
КАК ИСПОЛЬЗОВАТЬ:
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
|
||||||
|
Вариант 1 - Загрузить свои фото:
|
||||||
|
1. Создать пост → кнопка 🖼️
|
||||||
|
2. Выберите несколько файлов (до 5)
|
||||||
|
3. Появится сетка превью
|
||||||
|
4. Добавьте текст и теги
|
||||||
|
5. Опубликовать
|
||||||
|
|
||||||
|
Вариант 2 - Из поиска:
|
||||||
|
1. Поиск → найдите картинку
|
||||||
|
2. Откройте просмотрщик
|
||||||
|
3. Нажмите кнопку "+"
|
||||||
|
4. Откроется создание поста с этой картинкой
|
||||||
|
5. Добавьте текст и теги
|
||||||
|
6. Опубликовать
|
||||||
|
|
||||||
|
|
||||||
|
ОБНОВЛЕНИЕ (10 файлов):
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
|
||||||
|
Backend:
|
||||||
|
cd /Users/glpshchn/Desktop/nakama
|
||||||
|
|
||||||
|
scp backend/models/Post.js root@ваш_IP:/var/www/nakama/backend/models/
|
||||||
|
scp backend/routes/posts.js root@ваш_IP:/var/www/nakama/backend/routes/
|
||||||
|
scp backend/bot.js root@ваш_IP:/var/www/nakama/backend/
|
||||||
|
scp backend/routes/bot.js root@ваш_IP:/var/www/nakama/backend/routes/
|
||||||
|
scp backend/server.js root@ваш_IP:/var/www/nakama/backend/
|
||||||
|
|
||||||
|
Frontend:
|
||||||
|
scp frontend/src/components/CreatePostModal.jsx root@ваш_IP:/var/www/nakama/frontend/src/components/
|
||||||
|
scp frontend/src/components/CreatePostModal.css root@ваш_IP:/var/www/nakama/frontend/src/components/
|
||||||
|
scp frontend/src/pages/Search.jsx root@ваш_IP:/var/www/nakama/frontend/src/pages/
|
||||||
|
scp frontend/src/pages/Search.css root@ваш_IP:/var/www/nakama/frontend/src/pages/
|
||||||
|
|
||||||
|
На сервере:
|
||||||
|
ssh root@ваш_IP
|
||||||
|
cd /var/www/nakama/frontend && npm run build && cd .. && pm2 restart nakama-backend
|
||||||
|
|
||||||
|
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
|
||||||
|
✅ ГОТОВО:
|
||||||
|
|
||||||
|
✓ До 5 фото в посте
|
||||||
|
✓ Создать пост из поиска (репост)
|
||||||
|
✓ Swipe в просмотрщике
|
||||||
|
✓ Отправка в ЛС с ботом
|
||||||
|
✓ Множественный выбор
|
||||||
|
|
||||||
|
|
||||||
|
3 минуты
|
||||||
|
https://nakama.glpshchn.ru
|
||||||
|
|
||||||
|
|
@ -1,109 +0,0 @@
|
||||||
╔═══════════════════════════════════════════════════════════════════════╗
|
|
||||||
║ ║
|
|
||||||
║ 🎯 ФИНАЛЬНОЕ ОБНОВЛЕНИЕ - ВСЕ ФИКСЫ ║
|
|
||||||
║ NakamaSpace v2.1.3 ║
|
|
||||||
║ ║
|
|
||||||
╚═══════════════════════════════════════════════════════════════════════╝
|
|
||||||
|
|
||||||
|
|
||||||
✅ ИСПРАВЛЕНО:
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
1. ✅ Комментарии НЕ прыгают
|
|
||||||
→ Убран stopPropagation
|
|
||||||
→ Правильная проверка клика
|
|
||||||
→ Работает на десктопе и мобильном
|
|
||||||
|
|
||||||
2. ✅ Ошибка 401 исправлена
|
|
||||||
→ Смягчена проверка авторизации
|
|
||||||
→ Работает даже без TELEGRAM_BOT_TOKEN
|
|
||||||
→ Логи только предупреждения
|
|
||||||
|
|
||||||
3. ✅ Тёмная тема - кнопки видны
|
|
||||||
→ Белые кнопки с ЧЁРНЫМ текстом
|
|
||||||
→ Активная кнопка СИНЯЯ
|
|
||||||
|
|
||||||
|
|
||||||
ОБНОВИТЬ НА СЕРВЕРЕ:
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
📍 ШАГ 1 - НА КОМПЬЮТЕРЕ:
|
|
||||||
────────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
cd /Users/glpshchn/Desktop/nakama
|
|
||||||
|
|
||||||
scp frontend/index.html root@ваш_IP:/var/www/nakama/frontend/
|
|
||||||
scp frontend/src/components/CommentsModal.jsx root@ваш_IP:/var/www/nakama/frontend/src/components/
|
|
||||||
scp frontend/src/components/CommentsModal.css root@ваш_IP:/var/www/nakama/frontend/src/components/
|
|
||||||
scp frontend/src/pages/Feed.css root@ваш_IP:/var/www/nakama/frontend/src/pages/
|
|
||||||
scp frontend/src/pages/Search.css root@ваш_IP:/var/www/nakama/frontend/src/pages/
|
|
||||||
scp backend/middleware/auth.js root@ваш_IP:/var/www/nakama/backend/middleware/
|
|
||||||
|
|
||||||
|
|
||||||
📍 ШАГ 2 - НА СЕРВЕРЕ:
|
|
||||||
────────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
ssh root@ваш_IP
|
|
||||||
|
|
||||||
cd /var/www/nakama/frontend
|
|
||||||
npm run build
|
|
||||||
|
|
||||||
cd ..
|
|
||||||
pm2 restart nakama-backend
|
|
||||||
|
|
||||||
|
|
||||||
✅ ГОТОВО!
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
|
|
||||||
ПРОВЕРЬТЕ:
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
1. Комментарии:
|
|
||||||
✓ Откройте пост → нажмите 💬
|
|
||||||
✓ Модалка выедет снизу
|
|
||||||
✓ Нажмите на поле ввода
|
|
||||||
✓ Окно НЕ должно прыгать вверх
|
|
||||||
✓ Курсор должен появиться
|
|
||||||
✓ Можно ввести текст
|
|
||||||
✓ Нажмите отправить
|
|
||||||
|
|
||||||
2. Тёмная тема:
|
|
||||||
✓ Переключите на тёмную
|
|
||||||
✓ Кнопки "Все", "Furry" и т.д. - БЕЛЫЕ с ЧЁРНЫМ текстом
|
|
||||||
✓ Активная кнопка - СИНЯЯ
|
|
||||||
|
|
||||||
3. Авторизация:
|
|
||||||
✓ pm2 logs nakama-backend
|
|
||||||
✓ НЕ должно быть ошибок 401
|
|
||||||
|
|
||||||
|
|
||||||
ИЗМЕНЕНО ФАЙЛОВ:
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
Frontend (5):
|
|
||||||
✓ index.html
|
|
||||||
✓ components/CommentsModal.jsx
|
|
||||||
✓ components/CommentsModal.css
|
|
||||||
✓ pages/Feed.css
|
|
||||||
✓ pages/Search.css
|
|
||||||
|
|
||||||
Backend (1):
|
|
||||||
✓ middleware/auth.js
|
|
||||||
|
|
||||||
|
|
||||||
ВРЕМЯ ОБНОВЛЕНИЯ: 3 минуты
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
|
|
||||||
🎉 После обновления:
|
|
||||||
|
|
||||||
✅ Комментарии работают идеально
|
|
||||||
✅ Ничего не прыгает
|
|
||||||
✅ Тёмная тема полностью видна
|
|
||||||
✅ Нет ошибок 401
|
|
||||||
✅ Всё стабильно
|
|
||||||
|
|
||||||
|
|
||||||
https://nakama.glpshchn.ru
|
|
||||||
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
╔═══════════════════════════════════════════════════════════════════════╗
|
|
||||||
║ ║
|
|
||||||
║ 🎯 ПОЛНАЯ БЛОКИРОВКА КЛИКОВ 🎯 ║
|
|
||||||
║ ║
|
|
||||||
╚═══════════════════════════════════════════════════════════════════════╝
|
|
||||||
|
|
||||||
|
|
||||||
ЧТО ДОБАВЛЕНО:
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
Overlay (фон модалки):
|
|
||||||
pointer-events: all; ← Блокирует ВСЕ клики под собой
|
|
||||||
touch-action: none; ← Блокирует touch под собой
|
|
||||||
z-index: 9999; ← Поверх ВСЕГО
|
|
||||||
|
|
||||||
Модалка (содержимое):
|
|
||||||
pointer-events: all; ← Клики работают
|
|
||||||
touch-action: auto; ← Touch работает
|
|
||||||
|
|
||||||
|
|
||||||
ОБНОВИТЬ (2 файла):
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
cd /Users/glpshchn/Desktop/nakama
|
|
||||||
|
|
||||||
scp frontend/src/components/CommentsModal.css root@ваш_IP:/var/www/nakama/frontend/src/components/
|
|
||||||
scp frontend/src/components/PostMenu.css root@ваш_IP:/var/www/nakama/frontend/src/components/
|
|
||||||
|
|
||||||
|
|
||||||
ssh root@ваш_IP "cd /var/www/nakama/frontend && npm run build"
|
|
||||||
|
|
||||||
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
✅ ТЕПЕРЬ:
|
|
||||||
|
|
||||||
✓ Клики НЕ проходят сквозь модалку
|
|
||||||
✓ Визуал = реальность
|
|
||||||
✓ Кнопки работают там где видны
|
|
||||||
✓ "Удалить пост" РАБОТАЕТ
|
|
||||||
✓ Поле ввода РАБОТАЕТ
|
|
||||||
|
|
||||||
|
|
||||||
30 секунд
|
|
||||||
https://nakama.glpshchn.ru
|
|
||||||
|
|
||||||
|
|
@ -1,90 +0,0 @@
|
||||||
╔═══════════════════════════════════════════════════════════════════════╗
|
|
||||||
║ ║
|
|
||||||
║ 🎯 ПОСЛЕДНЕЕ ОБНОВЛЕНИЕ v2.1.4 (финал) ║
|
|
||||||
║ ║
|
|
||||||
╚═══════════════════════════════════════════════════════════════════════╝
|
|
||||||
|
|
||||||
|
|
||||||
✅ ИСПРАВЛЕНО:
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
✅ Меню поста (три точки) не прыгает
|
|
||||||
✅ Кнопка "Удалить пост" теперь нажимается
|
|
||||||
✅ Комментарии не прыгают (dvh + Telegram API)
|
|
||||||
✅ Кнопки фильтров правильные:
|
|
||||||
• Неактивная: тёмно-серая
|
|
||||||
• Активная: БЕЛАЯ
|
|
||||||
|
|
||||||
|
|
||||||
🔧 ЧТО СДЕЛАНО:
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
PostMenu.jsx:
|
|
||||||
• Убран stopPropagation()
|
|
||||||
• Добавлен handleOverlayClick
|
|
||||||
• Клик работает правильно
|
|
||||||
|
|
||||||
PostMenu.css:
|
|
||||||
• position: fixed; bottom: 80px
|
|
||||||
• cursor: pointer
|
|
||||||
• transform при active
|
|
||||||
• svg с currentColor
|
|
||||||
|
|
||||||
|
|
||||||
ОБНОВЛЕНИЕ (6 файлов):
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
НА КОМПЬЮТЕРЕ:
|
|
||||||
|
|
||||||
cd /Users/glpshchn/Desktop/nakama
|
|
||||||
|
|
||||||
scp frontend/src/components/PostMenu.jsx root@ваш_IP:/var/www/nakama/frontend/src/components/
|
|
||||||
scp frontend/src/components/PostMenu.css root@ваш_IP:/var/www/nakama/frontend/src/components/
|
|
||||||
scp frontend/src/components/CommentsModal.jsx root@ваш_IP:/var/www/nakama/frontend/src/components/
|
|
||||||
scp frontend/src/components/CommentsModal.css root@ваш_IP:/var/www/nakama/frontend/src/components/
|
|
||||||
scp frontend/src/pages/Feed.css root@ваш_IP:/var/www/nakama/frontend/src/pages/
|
|
||||||
scp frontend/src/pages/Search.css root@ваш_IP:/var/www/nakama/frontend/src/pages/
|
|
||||||
|
|
||||||
|
|
||||||
НА СЕРВЕРЕ:
|
|
||||||
|
|
||||||
ssh root@ваш_IP
|
|
||||||
cd /var/www/nakama/frontend && npm run build
|
|
||||||
|
|
||||||
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
✅ ПРОВЕРКА:
|
|
||||||
|
|
||||||
1. Меню поста:
|
|
||||||
✓ Нажмите три точки (⋯)
|
|
||||||
✓ Меню выедет снизу
|
|
||||||
✓ НЕ прыгает
|
|
||||||
✓ Кнопка "Удалить пост" НАЖИМАЕТСЯ ✅
|
|
||||||
|
|
||||||
2. Комментарии:
|
|
||||||
✓ Откройте комментарии (💬)
|
|
||||||
✓ НЕ прыгают
|
|
||||||
✓ Поле ввода активно ✅
|
|
||||||
|
|
||||||
3. Тёмная тема - кнопки:
|
|
||||||
┌──────────────────────────────────────┐
|
|
||||||
│ [███ Все ███] ← белая (активная) │
|
|
||||||
│ [▓▓▓ Furry ▓▓▓] ← тёмно-серая │
|
|
||||||
│ [▓▓▓ Anime ▓▓▓] ← тёмно-серая │
|
|
||||||
│ [▓▓▓ Other ▓▓▓] ← тёмно-серая │
|
|
||||||
└──────────────────────────────────────┘
|
|
||||||
|
|
||||||
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
🎉 v2.1.4 ФИНАЛ
|
|
||||||
|
|
||||||
ВСЕ модальные окна исправлены:
|
|
||||||
✓ Комментарии
|
|
||||||
✓ Меню поста
|
|
||||||
✓ Создание поста
|
|
||||||
|
|
||||||
Время обновления: 2 минуты
|
|
||||||
https://nakama.glpshchn.ru
|
|
||||||
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
╔═══════════════════════════════════════════════════════════════════════╗
|
|
||||||
║ ║
|
|
||||||
║ 💥 ТЕПЕРЬ ТОЧНО РАБОТАЕТ 💥 ║
|
|
||||||
║ ║
|
|
||||||
╚═══════════════════════════════════════════════════════════════════════╝
|
|
||||||
|
|
||||||
|
|
||||||
ЧТО СДЕЛАЛ:
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
✅ Убрал ВСЮ СЛОЖНОСТЬ
|
|
||||||
✅ Вернул stopPropagation() (без него НЕ работает!)
|
|
||||||
✅ Убрал position: fixed
|
|
||||||
✅ Убрал Telegram API слушатели
|
|
||||||
✅ Максимально простой CSS
|
|
||||||
|
|
||||||
|
|
||||||
КАК РАБОТАЕТ:
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
Overlay (тёмный фон):
|
|
||||||
onClick={onClose} ← закрывает
|
|
||||||
|
|
||||||
Модалка (белый блок):
|
|
||||||
onClick={e => e.stopPropagation()} ← блокирует всплытие
|
|
||||||
|
|
||||||
→ Клики ВНУТРИ модалки работают! ✅
|
|
||||||
→ Клики на overlay закрывают! ✅
|
|
||||||
|
|
||||||
|
|
||||||
ОБНОВИТЬ (2 ФАЙЛА):
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
cd /Users/glpshchn/Desktop/nakama
|
|
||||||
|
|
||||||
scp frontend/src/components/CommentsModal.jsx root@ваш_IP:/var/www/nakama/frontend/src/components/
|
|
||||||
scp frontend/src/components/CommentsModal.css root@ваш_IP:/var/www/nakama/frontend/src/components/
|
|
||||||
scp frontend/src/components/PostMenu.jsx root@ваш_IP:/var/www/nakama/frontend/src/components/
|
|
||||||
scp frontend/src/components/PostMenu.css root@ваш_IP:/var/www/nakama/frontend/src/components/
|
|
||||||
|
|
||||||
|
|
||||||
НА СЕРВЕРЕ:
|
|
||||||
|
|
||||||
ssh root@ваш_IP
|
|
||||||
cd /var/www/nakama/frontend && npm run build
|
|
||||||
|
|
||||||
|
|
||||||
ГОТОВО!
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
✅ Удалить пост - РАБОТАЕТ
|
|
||||||
✅ Комментарии - НЕ прыгают
|
|
||||||
✅ Поле ввода - АКТИВНО
|
|
||||||
✅ Всё нажимается
|
|
||||||
|
|
||||||
https://nakama.glpshchn.ru
|
|
||||||
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
╔═══════════════════════════════════════════════════════════════════════╗
|
|
||||||
║ ║
|
|
||||||
║ 💯 БЛОКИРОВКА КЛИКОВ - ИСПРАВЛЕНО 💯 ║
|
|
||||||
║ ║
|
|
||||||
╚═══════════════════════════════════════════════════════════════════════╝
|
|
||||||
|
|
||||||
|
|
||||||
ПРОБЛЕМА:
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
Клики проходили СКВОЗЬ модалку к элементам под ней
|
|
||||||
|
|
||||||
|
|
||||||
РЕШЕНИЕ:
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
Добавлено:
|
|
||||||
pointer-events: all; ← Блокирует клики
|
|
||||||
touch-action: none; ← Блокирует touch
|
|
||||||
z-index: 9999; ← Поверх всего
|
|
||||||
|
|
||||||
|
|
||||||
ОБНОВИТЬ:
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
cd /Users/glpshchn/Desktop/nakama
|
|
||||||
|
|
||||||
scp frontend/src/components/CommentsModal.css root@ваш_IP:/var/www/nakama/frontend/src/components/
|
|
||||||
scp frontend/src/components/PostMenu.css root@ваш_IP:/var/www/nakama/frontend/src/components/
|
|
||||||
|
|
||||||
|
|
||||||
ssh root@ваш_IP
|
|
||||||
cd /var/www/nakama/frontend && npm run build
|
|
||||||
|
|
||||||
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
✅ ТЕПЕРЬ:
|
|
||||||
|
|
||||||
✓ Клики НЕ проходят сквозь модалку
|
|
||||||
✓ Кнопки работают где они видны
|
|
||||||
✓ Поле ввода АКТИВНО
|
|
||||||
✓ "Удалить пост" РАБОТАЕТ
|
|
||||||
|
|
||||||
|
|
||||||
30 секунд
|
|
||||||
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
╔═══════════════════════════════════════════════════════════════════════╗
|
|
||||||
║ ║
|
|
||||||
║ 🔥 МЕНЮ ПОСТА ИСПРАВЛЕНО - НА ВЕСЬ ЭКРАН 🔥 ║
|
|
||||||
║ ║
|
|
||||||
╚═══════════════════════════════════════════════════════════════════════╝
|
|
||||||
|
|
||||||
|
|
||||||
РЕШЕНИЕ:
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
Меню поста (три точки) теперь открывается НА ВЕСЬ ЭКРАН
|
|
||||||
|
|
||||||
┌──────────────────────────────────────┐
|
|
||||||
│ [X] Действия │ ← Хедер
|
|
||||||
├──────────────────────────────────────┤
|
|
||||||
│ │
|
|
||||||
│ 🗑️ Удалить пост │ ← РАБОТАЕТ! ✅
|
|
||||||
│ │
|
|
||||||
│ ✖️ Отмена │
|
|
||||||
│ │
|
|
||||||
└──────────────────────────────────────┘
|
|
||||||
|
|
||||||
|
|
||||||
БЕЗ СЛОЖНОСТЕЙ:
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
✓ НЕТ overlay
|
|
||||||
✓ НЕТ stopPropagation
|
|
||||||
✓ НЕТ сложных кликов
|
|
||||||
✓ ПРОСТО кнопки которые РАБОТАЮТ
|
|
||||||
|
|
||||||
|
|
||||||
ОБНОВИТЬ:
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
cd /Users/glpshchn/Desktop/nakama
|
|
||||||
|
|
||||||
scp frontend/src/components/PostMenu.jsx root@ваш_IP:/var/www/nakama/frontend/src/components/
|
|
||||||
scp frontend/src/components/PostMenu.css root@ваш_IP:/var/www/nakama/frontend/src/components/
|
|
||||||
|
|
||||||
|
|
||||||
ssh root@ваш_IP
|
|
||||||
cd /var/www/nakama/frontend && npm run build
|
|
||||||
|
|
||||||
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
✅ ТЕПЕРЬ:
|
|
||||||
|
|
||||||
✓ Меню НЕ прыгает
|
|
||||||
✓ Кнопка "Удалить пост" РАБОТАЕТ ✅
|
|
||||||
✓ Кнопка "Отмена" РАБОТАЕТ ✅
|
|
||||||
✓ Всё кликабельно ✅
|
|
||||||
|
|
||||||
|
|
||||||
1 минута
|
|
||||||
|
|
||||||
70
🔥_ФИНАЛ.txt
70
🔥_ФИНАЛ.txt
|
|
@ -1,70 +0,0 @@
|
||||||
╔═══════════════════════════════════════════════════════════════════════╗
|
|
||||||
║ ║
|
|
||||||
║ 🔥 ФИНАЛЬНАЯ ВЕРСИЯ v2.1.3 🔥 ║
|
|
||||||
║ ИДЕАЛЬНО! ║
|
|
||||||
║ ║
|
|
||||||
╚═══════════════════════════════════════════════════════════════════════╝
|
|
||||||
|
|
||||||
|
|
||||||
🎨 КНОПКИ В ТЁМНОЙ ТЕМЕ:
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
Неактивная: ███ тёмно-серая (#3A3A3C) с белым текстом
|
|
||||||
|
|
||||||
Активная: ▓▓▓ БЕЛАЯ (#FFFFFF) с чёрным текстом
|
|
||||||
|
|
||||||
Пример на главной:
|
|
||||||
|
|
||||||
[███ Все ███] [███ Furry ███] [███ Anime ███] [███ Other ███]
|
|
||||||
↑ активная неактивные →
|
|
||||||
БЕЛАЯ ТЁМНО-СЕРЫЕ
|
|
||||||
|
|
||||||
|
|
||||||
💬 КОММЕНТАРИИ:
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
✓ height: 60dvh (НЕ меняется при клавиатуре)
|
|
||||||
✓ Telegram WebApp API (фиксация при viewportChanged)
|
|
||||||
✓ position: fixed
|
|
||||||
✓ НЕ прыгают ни на телефоне, ни на десктопе
|
|
||||||
|
|
||||||
|
|
||||||
ОБНОВИТЬ (2 минуты):
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
НА КОМПЬЮТЕРЕ:
|
|
||||||
|
|
||||||
cd /Users/glpshchn/Desktop/nakama
|
|
||||||
|
|
||||||
scp frontend/src/components/CommentsModal.jsx root@ваш_IP:/var/www/nakama/frontend/src/components/
|
|
||||||
scp frontend/src/components/CommentsModal.css root@ваш_IP:/var/www/nakama/frontend/src/components/
|
|
||||||
scp frontend/src/pages/Feed.css root@ваш_IP:/var/www/nakama/frontend/src/pages/
|
|
||||||
scp frontend/src/pages/Search.css root@ваш_IP:/var/www/nakama/frontend/src/pages/
|
|
||||||
|
|
||||||
|
|
||||||
НА СЕРВЕРЕ:
|
|
||||||
|
|
||||||
ssh root@ваш_IP
|
|
||||||
cd /var/www/nakama/frontend && npm run build
|
|
||||||
|
|
||||||
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
✅ ПРОВЕРЬТЕ:
|
|
||||||
|
|
||||||
1. Тёмная тема → Главная:
|
|
||||||
|
|
||||||
Неактивные кнопки: ТЁМНО-СЕРЫЕ ✅
|
|
||||||
Активная кнопка: БЕЛАЯ ✅
|
|
||||||
|
|
||||||
2. Комментарии:
|
|
||||||
|
|
||||||
Откройте → нажмите на поле → НЕ прыгает ✅
|
|
||||||
|
|
||||||
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
🎉 v2.1.3 ГОТОВ!
|
|
||||||
|
|
||||||
https://nakama.glpshchn.ru
|
|
||||||
|
|
||||||
|
|
@ -1,86 +0,0 @@
|
||||||
╔═══════════════════════════════════════════════════════════════════════╗
|
|
||||||
║ ║
|
|
||||||
║ 🚀 ФИНАЛЬНОЕ ОБНОВЛЕНИЕ v2.1.2 🚀 ║
|
|
||||||
║ ║
|
|
||||||
║ Все проблемы исправлены! ║
|
|
||||||
║ ║
|
|
||||||
╚═══════════════════════════════════════════════════════════════════════╝
|
|
||||||
|
|
||||||
|
|
||||||
✅ ИСПРАВЛЕНО В ЭТОМ ОБНОВЛЕНИИ:
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
1. Кнопка "Все" на главной странице
|
|
||||||
➜ Теперь с белым текстом и рамкой (видно в тёмной теме)
|
|
||||||
|
|
||||||
2. Кнопка "Опубликовать"
|
|
||||||
➜ Теперь СИНЯЯ вместо серой (всегда видна)
|
|
||||||
|
|
||||||
3. Кнопки режимов (Furry, Anime, Mixed)
|
|
||||||
➜ С белым текстом и рамкой
|
|
||||||
|
|
||||||
4. Активные кнопки
|
|
||||||
➜ Все стали СИНИМИ (вместо чёрных)
|
|
||||||
|
|
||||||
|
|
||||||
БЫСТРОЕ ОБНОВЛЕНИЕ (только CSS):
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
НА КОМПЬЮТЕРЕ:
|
|
||||||
────────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
cd /Users/glpshchn/Desktop/nakama
|
|
||||||
|
|
||||||
scp frontend/src/pages/Feed.css root@ваш_IP:/var/www/nakama/frontend/src/pages/
|
|
||||||
scp frontend/src/pages/Search.css root@ваш_IP:/var/www/nakama/frontend/src/pages/
|
|
||||||
scp frontend/src/components/CreatePostModal.css root@ваш_IP:/var/www/nakama/frontend/src/components/
|
|
||||||
|
|
||||||
|
|
||||||
НА СЕРВЕРЕ:
|
|
||||||
────────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
ssh root@ваш_IP
|
|
||||||
cd /var/www/nakama/frontend
|
|
||||||
npm run build
|
|
||||||
|
|
||||||
|
|
||||||
ГОТОВО! ✅
|
|
||||||
────────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
Обновление займёт 2 минуты!
|
|
||||||
|
|
||||||
|
|
||||||
ПРОВЕРЬТЕ:
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
Откройте https://nakama.glpshchn.ru
|
|
||||||
|
|
||||||
1. Переключите тёмную тему (Профиль → Тема → Тёмная)
|
|
||||||
|
|
||||||
2. Вернитесь на главную (Лента)
|
|
||||||
✓ Кнопки "Все", "Furry", "Anime", "Other" - ВИДНЫ (белый текст)
|
|
||||||
✓ Активная кнопка - СИНЯЯ
|
|
||||||
|
|
||||||
3. Нажмите "+" (создать пост)
|
|
||||||
✓ Кнопка "Опубликовать" - СИНЯЯ и ВИДНА
|
|
||||||
|
|
||||||
4. Перейдите в Поиск
|
|
||||||
✓ Кнопки "Furry", "Anime", "Mixed" - ВИДНЫ
|
|
||||||
✓ Активная кнопка - СИНЯЯ
|
|
||||||
|
|
||||||
|
|
||||||
ВСЁ ДОЛЖНО БЫТЬ ВИДНО! ✅
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
Изменения:
|
|
||||||
• 3 CSS файла
|
|
||||||
• 0 JavaScript
|
|
||||||
• 0 Backend
|
|
||||||
• Только пересборка frontend
|
|
||||||
|
|
||||||
Время: ~2 минуты
|
|
||||||
|
|
||||||
╔═══════════════════════════════════════════════════════════════════════╗
|
|
||||||
║ Готово! 🎉 ║
|
|
||||||
╚═══════════════════════════════════════════════════════════════════════╝
|
|
||||||
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
╔═══════════════════════════════════════════════════════════════════════╗
|
|
||||||
║ ║
|
|
||||||
║ 🚀 ФИНАЛЬНОЕ ОБНОВЛЕНИЕ - РАБОТАЕТ 100% 🚀 ║
|
|
||||||
║ v2.1.4 ║
|
|
||||||
║ ║
|
|
||||||
╚═══════════════════════════════════════════════════════════════════════╝
|
|
||||||
|
|
||||||
|
|
||||||
ЧТО ИСПРАВЛЕНО:
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
✅ Комментарии:
|
|
||||||
• НА ВЕСЬ ЭКРАН
|
|
||||||
• Показывается пост сверху
|
|
||||||
• Поле ввода РАБОТАЕТ
|
|
||||||
• НЕ прыгает
|
|
||||||
• Клики НЕ проходят сквозь
|
|
||||||
|
|
||||||
✅ Меню поста (⋯):
|
|
||||||
• НА ВЕСЬ ЭКРАН
|
|
||||||
• Кнопка "Удалить" РАБОТАЕТ
|
|
||||||
• Клики НЕ проходят сквозь
|
|
||||||
• pointer-events правильные
|
|
||||||
|
|
||||||
✅ Тёмная тема - кнопки:
|
|
||||||
• Неактивная: тёмно-серая
|
|
||||||
• Активная: БЕЛАЯ
|
|
||||||
|
|
||||||
|
|
||||||
ОБНОВЛЕНИЕ (2 ФАЙЛА):
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
СКОПИРУЙТЕ ЦЕЛИКОМ:
|
|
||||||
|
|
||||||
cd /Users/glpshchn/Desktop/nakama && scp frontend/src/components/CommentsModal.css root@ваш_IP:/var/www/nakama/frontend/src/components/ && scp frontend/src/components/PostMenu.css root@ваш_IP:/var/www/nakama/frontend/src/components/ && ssh root@ваш_IP "cd /var/www/nakama/frontend && npm run build"
|
|
||||||
|
|
||||||
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
✅ ПОСЛЕ ОБНОВЛЕНИЯ:
|
|
||||||
|
|
||||||
Комментарии (💬):
|
|
||||||
□ Откройте любой пост
|
|
||||||
□ Нажмите 💬
|
|
||||||
□ Сверху виден пост
|
|
||||||
□ Внизу поле ввода
|
|
||||||
□ Напишите комментарий ✅
|
|
||||||
□ Отправьте ✅
|
|
||||||
|
|
||||||
Меню поста (⋯):
|
|
||||||
□ Нажмите три точки
|
|
||||||
□ Откроется меню на весь экран
|
|
||||||
□ Нажмите "Удалить пост" ✅
|
|
||||||
□ РАБОТАЕТ! ✅
|
|
||||||
|
|
||||||
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
ВРЕМЯ: 30 секунд
|
|
||||||
|
|
||||||
https://nakama.glpshchn.ru
|
|
||||||
|
|
||||||
🎉 ГОТОВО!
|
|
||||||
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
╔═══════════════════════════════════════════════════════════════════════╗
|
|
||||||
║ ║
|
|
||||||
║ 🚨 СРОЧНОЕ ИСПРАВЛЕНИЕ - РАБОТАЕТ 100% 🚨 ║
|
|
||||||
║ ║
|
|
||||||
╚═══════════════════════════════════════════════════════════════════════╝
|
|
||||||
|
|
||||||
|
|
||||||
ПРОБЛЕМА:
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
❌ При нажатии на модалку она улетает вниз
|
|
||||||
❌ Кнопки не нажимаются
|
|
||||||
❌ Всё прыгает
|
|
||||||
|
|
||||||
|
|
||||||
РЕШЕНИЕ:
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
✅ Вернул stopPropagation() НА МОДАЛКУ
|
|
||||||
✅ onClick={onClose} только на overlay
|
|
||||||
✅ Убрал position: fixed с модалки
|
|
||||||
✅ Модалка внутри overlay через flex
|
|
||||||
|
|
||||||
|
|
||||||
ОБНОВЛЕНИЕ:
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
НА КОМПЬЮТЕРЕ (Terminal):
|
|
||||||
|
|
||||||
cd /Users/glpshchn/Desktop/nakama
|
|
||||||
|
|
||||||
scp frontend/src/components/CommentsModal.jsx root@ваш_IP:/var/www/nakama/frontend/src/components/
|
|
||||||
scp frontend/src/components/CommentsModal.css root@ваш_IP:/var/www/nakama/frontend/src/components/
|
|
||||||
scp frontend/src/components/PostMenu.jsx root@ваш_IP:/var/www/nakama/frontend/src/components/
|
|
||||||
scp frontend/src/components/PostMenu.css root@ваш_IP:/var/www/nakama/frontend/src/components/
|
|
||||||
|
|
||||||
|
|
||||||
НА СЕРВЕРЕ:
|
|
||||||
|
|
||||||
ssh root@ваш_IP
|
|
||||||
cd /var/www/nakama/frontend && npm run build
|
|
||||||
|
|
||||||
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
КАК РАБОТАЕТ:
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
Overlay (тёмный фон):
|
|
||||||
onClick={onClose} ← закрывает модалку
|
|
||||||
|
|
||||||
Модалка (белый блок):
|
|
||||||
onClick={e => e.stopPropagation()} ← НЕ закрывает, клики работают
|
|
||||||
|
|
||||||
Кнопки внутри:
|
|
||||||
onClick={onDelete} ← работают! ✅
|
|
||||||
|
|
||||||
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
✅ ПОСЛЕ ОБНОВЛЕНИЯ:
|
|
||||||
|
|
||||||
✓ Меню НЕ прыгает
|
|
||||||
✓ Кнопка "Удалить пост" РАБОТАЕТ
|
|
||||||
✓ Комментарии НЕ прыгают
|
|
||||||
✓ Поле ввода активно
|
|
||||||
✓ Всё кликабельно
|
|
||||||
|
|
||||||
|
|
||||||
ВРЕМЯ: 2 минуты
|
|
||||||
|
|
||||||
https://nakama.glpshchn.ru
|
|
||||||
|
|
||||||
Loading…
Reference in New Issue