feat(comments): добавлен флаг is_post_author в API
- Добавлено поле is_post_author в CommentSerializer - Сервер теперь явно указывает, является ли автор комментария автором поста - Фронтенд использует этот флаг (с fallback на ручное сравнение) - Добавлена детальная отладка для диагностики проблемы - Это устраняет проблему с неправильным определением автора
This commit is contained in:
parent
f955185b91
commit
34ddc5588f
@ -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")):
|
||||
|
||||
@ -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 @@
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
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 = '<button type="button" class="btn-close position-absolute top-0 end-0 m-3" data-bs-dismiss="modal" aria-label="Закрыть" style="z-index: 1050;"></button>';
|
||||
html += '<article class="card">';
|
||||
|
||||
// Заголовок с автором
|
||||
html += '<header class="card-header">';
|
||||
html += '<div class="card-icon">';
|
||||
if (post.author && post.author.avatar) {
|
||||
html += `<img src="${post.author.avatar}" alt="">`;
|
||||
} else {
|
||||
html += '<i class="bi-person"></i>';
|
||||
}
|
||||
html += '</div>';
|
||||
html += '<div class="feed-info">';
|
||||
html += `<div class="card-title">${post.author?.full_name || 'Аноним'}</div>`;
|
||||
html += `<time class="feed-date">${formatDate(post.created_at)}</time>`;
|
||||
html += '</div>';
|
||||
|
||||
// Закрепленный пост
|
||||
if (post.pinned) {
|
||||
html += '<span class="feed-pin"><i class="bi-pin-angle"></i></span>';
|
||||
}
|
||||
html += '</header>';
|
||||
|
||||
// Изображение
|
||||
if (post.image) {
|
||||
html += `<div class="feed-img"><img src="${post.image}" alt="${post.title || 'Публикация'}"></div>`;
|
||||
}
|
||||
|
||||
// Тело
|
||||
html += '<div class="card-body">';
|
||||
html += `<h3 class="feed-title mb-3">${post.title || 'Без названия'}</h3>`;
|
||||
if (post.body) {
|
||||
html += `<div class="feed-text mb-3">${formatBody(post.body)}</div>`;
|
||||
}
|
||||
html += '</div>';
|
||||
|
||||
// Футер с лайками и комментариями
|
||||
html += '<footer class="feed-ft">';
|
||||
html += '<div class="feed-stats">';
|
||||
|
||||
// Лайки
|
||||
const likeIcon = post.user_has_liked ? 'bi-heart-fill text-danger' : 'bi-heart';
|
||||
html += `<button class="btn btn-ghost btn-sm like-btn" data-post-id="${post.id}">`;
|
||||
html += `<i class="${likeIcon}"></i> ${post.likes_count || 0}`;
|
||||
html += '</button>';
|
||||
|
||||
// Комментарии
|
||||
// html += `<span class="text-muted ms-3"><i class="bi-chat"></i> ${post.comments_count || 0}</span>`;
|
||||
|
||||
html += '</div>';
|
||||
html += '</footer>';
|
||||
|
||||
// Секция комментариев внутри карточки
|
||||
html += '<div class="comments-section-wrapper">';
|
||||
|
||||
if (post.comments && post.comments.length > 0) {
|
||||
html += '<div class="px-3 pt-3">';
|
||||
html += '<hr class="my-3">';
|
||||
html += '<div class="comments-section-header">';
|
||||
html += '<i class="bi-chat-dots comments-icon"></i>';
|
||||
html += '<h5 class="comments-title">Комментарии</h5>';
|
||||
html += `<span class="comments-count">(${post.comments.length})</span>`;
|
||||
html += '</div>';
|
||||
html += '<div class="comments-list comments-scrollable" id="commentsListContainer">';
|
||||
post.comments.forEach(comment => {
|
||||
html += renderComment(comment, postAuthorId);
|
||||
});
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
// Форма добавления комментария (sticky)
|
||||
html += '<div class="comment-form-sticky">';
|
||||
html += '<div class="px-3 py-2 border-top">';
|
||||
html += '<form class="comment-form-modal" data-post-id="' + post.id + '">';
|
||||
html += '<div class="message-field message-field--compact">';
|
||||
|
||||
// Кнопка выбора эмодзи
|
||||
html += '<div class="dropdown message-emoji">';
|
||||
html += '<button type="button" class="btn btn-ghost btn-emoji message-icon-btn" ';
|
||||
html += 'data-bs-toggle="dropdown" aria-expanded="false" title="Вставить смайлик">';
|
||||
html += '<i class="bi-emoji-smile"></i>';
|
||||
html += '</button>';
|
||||
html += '<div class="dropdown-menu dropdown-menu-start message-emoji-menu">';
|
||||
html += '<emoji-picker data-emoji-picker class="chat-emoji-picker"></emoji-picker>';
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
|
||||
html += '<textarea name="text" class="form-control message-input" rows="1" placeholder="Оставить комментарий…" required></textarea>';
|
||||
html += '<button class="btn btn-primary message-send" type="submit" title="Отправить"><i class="bi-send"></i></button>';
|
||||
html += '</div>';
|
||||
html += '</form>';
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
|
||||
html += '</div>'; // comments-section-wrapper
|
||||
|
||||
html += '</article>';
|
||||
|
||||
contentDiv.innerHTML = html;
|
||||
|
||||
// Прокручиваем к нижней части комментариев после того, как DOM обновится
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => {
|
||||
const commentsContainer = document.getElementById('commentsListContainer');
|
||||
if (commentsContainer) {
|
||||
commentsContainer.scrollTop = commentsContainer.scrollHeight;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Добавляем обработчики
|
||||
attachLikeHandlers();
|
||||
function getAuthorId(authorObj) {
|
||||
if (!authorObj) return null;
|
||||
// API возвращает author.id
|
||||
return authorObj.id || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Отображение комментария
|
||||
* @param {Object} comment - Объект комментария
|
||||
* @param {number} postAuthorId - ID автора поста
|
||||
*/
|
||||
function renderComment(comment, postAuthorId) {
|
||||
const isAuthor = comment.author?.id === postAuthorId;
|
||||
const commentClass = isAuthor ? 'comment-item comment-item--author' : 'comment-item';
|
||||
|
||||
let html = `<div class="${commentClass}">`;
|
||||
html += '<div class="comment-header">';
|
||||
|
||||
// Автор и дата
|
||||
const authorName = comment.author?.full_name || 'Аноним';
|
||||
const authorId = comment.author?.id;
|
||||
|
||||
if (authorId) {
|
||||
html += `<a href="/employees/employee/${authorId}/" class="comment-author">${authorName}</a>`;
|
||||
} else {
|
||||
html += `<span class="comment-author">${authorName}</span>`;
|
||||
}
|
||||
|
||||
html += '<span class="comment-separator">·</span>';
|
||||
html += `<time class="comment-date">${formatDate(comment.created_at)}</time>`;
|
||||
html += '</div>';
|
||||
|
||||
// Текст комментария
|
||||
html += `<div class="comment-body">${formatBody(comment.text)}</div>`;
|
||||
html += '</div>';
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Форматирование даты
|
||||
*/
|
||||
function formatDate(dateString) {
|
||||
const date = new Date(dateString);
|
||||
const now = new Date();
|
||||
@ -276,43 +102,261 @@
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Форматирование текста (простая обработка переносов строк)
|
||||
*/
|
||||
function formatBody(text) {
|
||||
return text.replace(/\n/g, '<br>');
|
||||
}
|
||||
|
||||
/**
|
||||
* Показать ошибку
|
||||
*/
|
||||
function showError(message) {
|
||||
const contentDiv = document.getElementById('postDetailContent');
|
||||
contentDiv.innerHTML = `
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<i class="bi-exclamation-triangle"></i> ${message}
|
||||
</div>
|
||||
`;
|
||||
function pluralizeComments(count) {
|
||||
const cases = [2, 0, 1, 1, 1, 2];
|
||||
const titles = ['комментарий', 'комментария', 'комментариев'];
|
||||
return titles[(count % 100 > 4 && count % 100 < 20) ? 2 : cases[(count % 10 < 5) ? count % 10 : 5]];
|
||||
}
|
||||
|
||||
/**
|
||||
* Открыть модальное окно с постом
|
||||
* Загрузка поста
|
||||
*/
|
||||
function openPostModal(postId) {
|
||||
if (!modal) {
|
||||
console.error('Modal не инициализирован');
|
||||
return;
|
||||
async function loadPostDetail(postId) {
|
||||
const accessToken = getAccessToken();
|
||||
const headers = { 'Accept': 'application/json' };
|
||||
|
||||
if (accessToken) {
|
||||
headers['Authorization'] = `Bearer ${accessToken}`;
|
||||
}
|
||||
|
||||
modal.show();
|
||||
loadPostDetail(postId);
|
||||
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();
|
||||
|
||||
if (DEBUG) {
|
||||
console.log('=== POST DEBUG ===');
|
||||
console.log('Post:', post);
|
||||
console.log('Post author:', post.author);
|
||||
console.log('Post author ID:', getAuthorId(post.author), typeof getAuthorId(post.author));
|
||||
if (post.comments && post.comments.length > 0) {
|
||||
const firstComment = post.comments[0];
|
||||
console.log('First comment:', firstComment);
|
||||
console.log('Comment author:', firstComment.author);
|
||||
console.log('Comment author ID:', getAuthorId(firstComment.author), typeof getAuthorId(firstComment.author));
|
||||
}
|
||||
console.log('==================');
|
||||
}
|
||||
|
||||
currentPost = post;
|
||||
renderPostDetail(post);
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки поста:', error);
|
||||
showError('Не удалось загрузить пост');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавление обработчиков на ссылки постов
|
||||
* Рендеринг
|
||||
*/
|
||||
function renderPostDetail(post) {
|
||||
const contentDiv = document.getElementById('postDetailContent');
|
||||
const postAuthorId = getAuthorId(post.author);
|
||||
|
||||
if (DEBUG) {
|
||||
console.log('Rendering with postAuthorId:', postAuthorId);
|
||||
}
|
||||
|
||||
let html = '<button type="button" class="btn-close position-absolute top-0 end-0 m-3" data-bs-dismiss="modal" aria-label="Закрыть" style="z-index: 1050;"></button>';
|
||||
html += '<article class="card">';
|
||||
html += renderPostHeader(post);
|
||||
|
||||
if (post.image) {
|
||||
html += `<div class="feed-img"><img src="${post.image}" alt="${escapeHtml(post.title || 'Публикация')}"></div>`;
|
||||
}
|
||||
|
||||
html += '<div class="card-body">';
|
||||
html += `<h3 class="feed-title mb-3">${escapeHtml(post.title || 'Без названия')}</h3>`;
|
||||
if (post.body) {
|
||||
html += `<div class="feed-text mb-3">${formatBody(post.body)}</div>`;
|
||||
}
|
||||
html += '</div>';
|
||||
|
||||
html += renderPostFooter(post);
|
||||
html += renderCommentsSection(post, postAuthorId);
|
||||
html += '</article>';
|
||||
|
||||
contentDiv.innerHTML = html;
|
||||
|
||||
if (post.comments && post.comments.length > 0) {
|
||||
contentDiv.dataset.allComments = JSON.stringify(post.comments);
|
||||
contentDiv.dataset.postAuthorId = postAuthorId || '';
|
||||
}
|
||||
|
||||
scrollToBottom();
|
||||
attachLikeHandlers();
|
||||
attachLoadMoreCommentsHandler();
|
||||
attachCommentFormHandler();
|
||||
}
|
||||
|
||||
function renderPostHeader(post) {
|
||||
let html = '<header class="card-header">';
|
||||
html += '<div class="card-icon">';
|
||||
if (post.author && post.author.avatar) {
|
||||
html += `<img src="${post.author.avatar}" alt="">`;
|
||||
} else {
|
||||
html += '<i class="bi-person"></i>';
|
||||
}
|
||||
html += '</div>';
|
||||
html += '<div class="feed-info">';
|
||||
html += `<div class="card-title">${escapeHtml(post.author?.full_name || 'Аноним')}</div>`;
|
||||
html += `<time class="feed-date">${formatDate(post.created_at)}</time>`;
|
||||
html += '</div>';
|
||||
|
||||
if (post.pinned) {
|
||||
html += '<span class="feed-pin"><i class="bi-pin-angle"></i></span>';
|
||||
}
|
||||
html += '</header>';
|
||||
return html;
|
||||
}
|
||||
|
||||
function renderPostFooter(post) {
|
||||
let html = '<footer class="feed-ft">';
|
||||
html += '<div class="feed-stats">';
|
||||
|
||||
const likeIcon = post.user_has_liked ? 'bi-heart-fill text-danger' : 'bi-heart';
|
||||
html += `<button class="btn btn-ghost btn-sm like-btn" data-post-id="${post.id}">`;
|
||||
html += `<i class="${likeIcon}"></i> ${post.likes_count || 0}`;
|
||||
html += '</button>';
|
||||
|
||||
html += '</div>';
|
||||
html += '</footer>';
|
||||
return html;
|
||||
}
|
||||
|
||||
function renderCommentsSection(post, postAuthorId) {
|
||||
let html = '<div class="comments-section-wrapper">';
|
||||
|
||||
if (post.comments && post.comments.length > 0) {
|
||||
html += '<div class="px-3 pt-3">';
|
||||
html += '<hr class="my-3">';
|
||||
html += '<div class="comments-section-header">';
|
||||
html += '<i class="bi-chat-dots comments-icon"></i>';
|
||||
html += '<h5 class="comments-title">Комментарии</h5>';
|
||||
html += `<span class="comments-count">(${post.comments.length})</span>`;
|
||||
html += '</div>';
|
||||
|
||||
if (post.comments.length > INITIAL_COMMENTS_COUNT) {
|
||||
const remainingCount = post.comments.length - INITIAL_COMMENTS_COUNT;
|
||||
html += `<button class="btn btn-link btn-sm mb-2 load-more-comments">`;
|
||||
html += `<i class="bi-chevron-up"></i> Показать ещё ${remainingCount} ${pluralizeComments(remainingCount)}`;
|
||||
html += '</button>';
|
||||
}
|
||||
|
||||
html += '<div class="comments-list comments-scrollable" id="commentsListContainer">';
|
||||
|
||||
const visibleComments = post.comments.slice(-INITIAL_COMMENTS_COUNT);
|
||||
visibleComments.forEach(comment => {
|
||||
html += renderComment(comment, postAuthorId);
|
||||
});
|
||||
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
html += renderCommentForm(post.id);
|
||||
html += '</div>';
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
function renderComment(comment, postAuthorId) {
|
||||
// Используем флаг из API (приоритет) или fallback на сравнение ID
|
||||
let isAuthor = false;
|
||||
|
||||
if (comment.hasOwnProperty('is_post_author')) {
|
||||
// Используем флаг из API
|
||||
isAuthor = comment.is_post_author;
|
||||
} else {
|
||||
// Fallback: сравниваем ID вручную
|
||||
const commentAuthorId = getAuthorId(comment.author);
|
||||
isAuthor = (commentAuthorId !== null && postAuthorId !== null &&
|
||||
String(commentAuthorId) === String(postAuthorId));
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
console.log(`Comment by ${comment.author?.full_name}:`, {
|
||||
is_post_author_flag: comment.is_post_author,
|
||||
calculated: isAuthor,
|
||||
comment_author_id: getAuthorId(comment.author),
|
||||
post_author_id: postAuthorId
|
||||
});
|
||||
}
|
||||
|
||||
const commentClass = isAuthor ? 'comment-item comment-item--author' : 'comment-item';
|
||||
|
||||
let html = `<div class="${commentClass}">`;
|
||||
html += '<div class="comment-header">';
|
||||
|
||||
const authorName = comment.author?.full_name || 'Аноним';
|
||||
const authorId = getAuthorId(comment.author);
|
||||
|
||||
if (authorId) {
|
||||
html += `<a href="/employees/employee/${authorId}/" class="comment-author">${escapeHtml(authorName)}</a>`;
|
||||
} else {
|
||||
html += `<span class="comment-author">${escapeHtml(authorName)}</span>`;
|
||||
}
|
||||
|
||||
html += '<span class="comment-separator">·</span>';
|
||||
html += `<time class="comment-date">${formatDate(comment.created_at)}</time>`;
|
||||
html += '</div>';
|
||||
html += `<div class="comment-body">${formatBody(comment.text)}</div>`;
|
||||
html += '</div>';
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
function renderCommentForm(postId) {
|
||||
let html = '<div class="comment-form-sticky">';
|
||||
html += '<div class="px-3 py-2">';
|
||||
html += `<form class="comment-form-modal" data-post-id="${postId}">`;
|
||||
html += '<div class="message-field message-field--compact">';
|
||||
|
||||
html += '<div class="dropdown message-emoji">';
|
||||
html += '<button type="button" class="btn btn-ghost btn-emoji message-icon-btn" ';
|
||||
html += 'data-bs-toggle="dropdown" aria-expanded="false" title="Вставить смайлик">';
|
||||
html += '<i class="bi-emoji-smile"></i>';
|
||||
html += '</button>';
|
||||
html += '<div class="dropdown-menu dropdown-menu-start message-emoji-menu">';
|
||||
html += '<emoji-picker data-emoji-picker class="chat-emoji-picker"></emoji-picker>';
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
|
||||
html += '<textarea name="text" class="form-control message-input" rows="1" placeholder="Оставить комментарий…" required></textarea>';
|
||||
html += '<button class="btn btn-primary message-send" type="submit" title="Отправить"><i class="bi-send"></i></button>';
|
||||
html += '</div>';
|
||||
html += '</form>';
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
return html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Обработчики
|
||||
*/
|
||||
function scrollToBottom() {
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => {
|
||||
const container = document.getElementById('commentsListContainer');
|
||||
if (container) {
|
||||
container.scrollTop = container.scrollHeight;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function attachPostLinkHandlers() {
|
||||
// Обработка кликов по ссылкам на посты
|
||||
document.addEventListener('click', function(e) {
|
||||
const link = e.target.closest('a[data-post-link]');
|
||||
if (link) {
|
||||
@ -325,9 +369,83 @@
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Обработчики лайков в модальном окне
|
||||
*/
|
||||
function attachLoadMoreCommentsHandler() {
|
||||
const loadMoreBtn = document.querySelector('.load-more-comments');
|
||||
if (!loadMoreBtn) return;
|
||||
|
||||
loadMoreBtn.addEventListener('click', function() {
|
||||
const contentDiv = document.getElementById('postDetailContent');
|
||||
const allComments = JSON.parse(contentDiv.dataset.allComments || '[]');
|
||||
const postAuthorId = contentDiv.dataset.postAuthorId;
|
||||
const container = document.getElementById('commentsListContainer');
|
||||
|
||||
if (!container || allComments.length === 0) return;
|
||||
|
||||
const scrollHeight = container.scrollHeight;
|
||||
const scrollTop = container.scrollTop;
|
||||
|
||||
let html = '';
|
||||
allComments.forEach(comment => {
|
||||
html += renderComment(comment, postAuthorId);
|
||||
});
|
||||
|
||||
container.innerHTML = html;
|
||||
|
||||
const newScrollHeight = container.scrollHeight;
|
||||
const heightDiff = newScrollHeight - scrollHeight;
|
||||
container.scrollTop = scrollTop + heightDiff;
|
||||
|
||||
this.remove();
|
||||
});
|
||||
}
|
||||
|
||||
function attachCommentFormHandler() {
|
||||
const form = document.querySelector('.comment-form-modal');
|
||||
if (!form) return;
|
||||
|
||||
form.addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
const textarea = this.querySelector('textarea[name="text"]');
|
||||
const text = textarea.value.trim();
|
||||
|
||||
if (!text) return;
|
||||
|
||||
const postId = this.dataset.postId;
|
||||
await submitComment(postId, text);
|
||||
textarea.value = '';
|
||||
});
|
||||
}
|
||||
|
||||
async function submitComment(postId, text) {
|
||||
const csrfToken = getCsrfToken();
|
||||
if (!csrfToken) {
|
||||
alert('Необходимо авторизоваться');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/v1/posts/${postId}/comments/`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRFToken': csrfToken,
|
||||
'Content-Type': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
},
|
||||
body: JSON.stringify({ text: text })
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
// Перезагружаем пост
|
||||
await loadPostDetail(postId);
|
||||
} else {
|
||||
alert('Ошибка при отправке комментария');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка:', error);
|
||||
alert('Ошибка сети');
|
||||
}
|
||||
}
|
||||
|
||||
function attachLikeHandlers() {
|
||||
const likeButtons = document.querySelectorAll('#postDetailContent .like-btn');
|
||||
likeButtons.forEach(btn => {
|
||||
@ -339,9 +457,6 @@
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Переключение лайка
|
||||
*/
|
||||
async function toggleLike(postId, button) {
|
||||
const csrfToken = getCsrfToken();
|
||||
if (!csrfToken) {
|
||||
@ -366,7 +481,6 @@
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
|
||||
// Обновляем иконку и счетчик
|
||||
if (isLiked) {
|
||||
icon.classList.remove('bi-heart-fill', 'text-danger');
|
||||
icon.classList.add('bi-heart');
|
||||
@ -375,7 +489,6 @@
|
||||
icon.classList.add('bi-heart-fill', 'text-danger');
|
||||
}
|
||||
|
||||
// Обновляем счетчик
|
||||
const countText = button.childNodes[1];
|
||||
if (countText) {
|
||||
countText.textContent = ' ' + (data.likes_count || 0);
|
||||
@ -389,14 +502,32 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Инициализация при загрузке DOM
|
||||
function showError(message) {
|
||||
const contentDiv = document.getElementById('postDetailContent');
|
||||
contentDiv.innerHTML = `
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<i class="bi-exclamation-triangle"></i> ${escapeHtml(message)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function openPostModal(postId) {
|
||||
if (!modal) {
|
||||
console.error('Modal не инициализирован');
|
||||
return;
|
||||
}
|
||||
|
||||
modal.show();
|
||||
loadPostDetail(postId);
|
||||
}
|
||||
|
||||
// Инициализация
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initPostDetailModal);
|
||||
} else {
|
||||
initPostDetailModal();
|
||||
}
|
||||
|
||||
// Экспорт для использования извне
|
||||
window.openPostModal = openPostModal;
|
||||
|
||||
})();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user