From 90e20051a06024a4fcd6042093f768841ea4a895 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=B3=D0=BE=D1=80=D1=8C=20=D0=9D=D0=B0=D0=B4=D0=B5?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Sat, 29 Nov 2025 16:15:39 +0300 Subject: [PATCH] =?UTF-8?q?feat(comments):=20=D0=B4=D0=BE=D0=B1=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=20=D1=84=D0=BB=D0=B0=D0=B3=20is=5Fpost?= =?UTF-8?q?=5Fauthor=20=D0=B2=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Добавлено поле is_post_author в CommentSerializer - Сервер теперь явно указывает, является ли автор комментария автором поста - Фронтенд использует этот флаг (с fallback на ручное сравнение) - Добавлена детальная отладка для диагностики проблемы - Это устраняет проблему с неправильным определением автора --- backend/api/v1/feed/serializers.py | 8 + .../static/js/components/postDetailModal.js | 581 +++++++++++------- 2 files changed, 364 insertions(+), 225 deletions(-) diff --git a/backend/api/v1/feed/serializers.py b/backend/api/v1/feed/serializers.py index 797c3051..68e93495 100644 --- a/backend/api/v1/feed/serializers.py +++ b/backend/api/v1/feed/serializers.py @@ -161,6 +161,7 @@ class CommentSerializer(serializers.ModelSerializer): created_at_display = serializers.DateTimeField( source="created_at", format="%d.%m.%Y %H:%M", read_only=True ) + is_post_author = serializers.SerializerMethodField() class Meta: model = Comment @@ -175,12 +176,19 @@ class CommentSerializer(serializers.ModelSerializer): "attachment", "created_at", "created_at_display", + "is_post_author", ] extra_kwargs = { "post": {"write_only": True}, "text": {"required": False, "allow_blank": True}, } + def get_is_post_author(self, obj): + """Проверяет, является ли автор комментария автором поста""" + if not obj.author or not obj.post or not obj.post.author: + return False + return obj.author.id == obj.post.author.id + def validate(self, attrs): text = (attrs.get("text") or "").strip() if not (text or attrs.get("image") or attrs.get("attachment")): diff --git a/backend/static/js/components/postDetailModal.js b/backend/static/js/components/postDetailModal.js index b884e8eb..f6cdd2f7 100644 --- a/backend/static/js/components/postDetailModal.js +++ b/backend/static/js/components/postDetailModal.js @@ -1,16 +1,23 @@ /** * postDetailModal.js * Модуль для открытия деталей поста в модальном окне + * Полностью переработанная версия */ (function() { 'use strict'; + // Константы + const INITIAL_COMMENTS_COUNT = 10; + const DEBUG = true; + + // Состояние let modal = null; let modalElement = null; + let currentPost = null; /** - * Инициализация модального окна + * Инициализация */ function initPostDetailModal() { modalElement = document.getElementById('postDetailModal'); @@ -21,7 +28,6 @@ modal = new bootstrap.Modal(modalElement); - // Очистка при закрытии modalElement.addEventListener('hidden.bs.modal', function() { const contentDiv = document.getElementById('postDetailContent'); if (contentDiv) { @@ -33,14 +39,14 @@ `; } + currentPost = null; }); - // Добавляем обработчики на ссылки постов attachPostLinkHandlers(); } /** - * Получение CSRF токена из cookie + * Утилиты для работы с cookies */ function getCsrfToken() { const cookies = document.cookie.split(';'); @@ -53,9 +59,6 @@ return null; } - /** - * Получение JWT токена из cookie (для загрузки данных) - */ function getAccessToken() { const cookies = document.cookie.split(';'); for (let cookie of cookies) { @@ -68,197 +71,20 @@ } /** - * Загрузка деталей поста через API + * Утилиты */ - async function loadPostDetail(postId) { - const accessToken = getAccessToken(); - const headers = { - 'Accept': 'application/json' - }; - - if (accessToken) { - headers['Authorization'] = `Bearer ${accessToken}`; - } - - try { - const response = await fetch(`/api/v1/posts/${postId}/`, { - method: 'GET', - headers: headers - }); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - const post = await response.json(); - renderPostDetail(post); - } catch (error) { - console.error('Ошибка загрузки поста:', error); - showError('Не удалось загрузить пост'); - } + function escapeHtml(text) { + const div = document.createElement('div'); + div.textContent = text; + return div.innerHTML; } - /** - * Отображение деталей поста в модальном окне - */ - function renderPostDetail(post) { - const contentDiv = document.getElementById('postDetailContent'); - - // Сохраняем ID автора для определения его комментариев - const postAuthorId = post.author?.id; - - // Формируем HTML с кнопкой закрытия - let html = ''; - html += '
'; - - // Заголовок с автором - html += '
'; - html += '
'; - if (post.author && post.author.avatar) { - html += ``; - } else { - html += ''; - } - html += '
'; - html += '
'; - html += `
${post.author?.full_name || 'Аноним'}
`; - html += ``; - html += '
'; - - // Закрепленный пост - if (post.pinned) { - html += ''; - } - html += '
'; - - // Изображение - if (post.image) { - html += `
${post.title || 'Публикация'}
`; - } - - // Тело - html += '
'; - html += `

${post.title || 'Без названия'}

`; - if (post.body) { - html += `
${formatBody(post.body)}
`; - } - html += '
'; - - // Футер с лайками и комментариями - html += '
'; - html += '
'; - - // Лайки - const likeIcon = post.user_has_liked ? 'bi-heart-fill text-danger' : 'bi-heart'; - html += `'; - - // Комментарии - // html += ` ${post.comments_count || 0}`; - - html += '
'; - html += '
'; - - // Секция комментариев внутри карточки - html += '
'; - - if (post.comments && post.comments.length > 0) { - html += '
'; - html += '
'; - html += '
'; - html += ''; - html += '
Комментарии
'; - html += `(${post.comments.length})`; - html += '
'; - html += '
'; - post.comments.forEach(comment => { - html += renderComment(comment, postAuthorId); - }); - html += '
'; - html += '
'; - } - - // Форма добавления комментария (sticky) - html += '
'; - html += '
'; - html += '
'; - html += '
'; - - // Кнопка выбора эмодзи - html += '