129 lines
3.9 KiB
JavaScript
129 lines
3.9 KiB
JavaScript
/**
|
||
* likeHandler.js
|
||
* Глобальный обработчик лайков для постов
|
||
* Работает через data-атрибуты и поддерживает AJAX-запросы
|
||
*
|
||
* @module likeHandler
|
||
* @version 1.0.0
|
||
*/
|
||
|
||
import { getCookie } from '../utils/stringUtils.js';
|
||
|
||
/**
|
||
* Инициализация глобального обработчика лайков
|
||
* Работает через делегирование событий на document
|
||
*/
|
||
export function initLikeHandler() {
|
||
document.addEventListener('click', handleLikeClick, { passive: false });
|
||
console.log('likeHandler: initialized');
|
||
|
||
return {
|
||
destroy: () => document.removeEventListener('click', handleLikeClick)
|
||
};
|
||
}
|
||
|
||
/**
|
||
* Обработчик клика на кнопку лайка
|
||
* @param {MouseEvent} e - Событие клика
|
||
*/
|
||
async function handleLikeClick(e) {
|
||
const btn = e.target.closest('[data-like-post]');
|
||
if (!btn) return;
|
||
|
||
e.preventDefault();
|
||
|
||
const url = btn.getAttribute('data-like-url');
|
||
if (!url || btn.disabled) return;
|
||
|
||
// Определяем текущее состояние и операцию
|
||
const currentlyLiked =
|
||
btn.classList.contains('is-liked') ||
|
||
btn.getAttribute('aria-pressed') === 'true';
|
||
const op = currentlyLiked ? 'unlike' : 'like';
|
||
|
||
btn.disabled = true;
|
||
try {
|
||
const resp = await fetch(url, {
|
||
method: 'POST',
|
||
headers: {
|
||
'X-CSRFToken': getCookie('csrftoken'),
|
||
'Accept': 'application/json',
|
||
'X-Requested-With': 'XMLHttpRequest'
|
||
},
|
||
body: new URLSearchParams({ op })
|
||
});
|
||
|
||
if (!resp.ok) throw new Error('HTTP ' + resp.status);
|
||
|
||
const data = await resp.json().catch(() => ({}));
|
||
|
||
// Совместимость с разными ключами API
|
||
const liked = (data.liked !== undefined) ? !!data.liked
|
||
: (data.is_liked !== undefined) ? !!data.is_liked
|
||
: !currentlyLiked; // fallback - инвертируем
|
||
|
||
const count = (data.likes !== undefined) ? data.likes
|
||
: (data.likes_count !== undefined) ? data.likes_count
|
||
: null;
|
||
|
||
// Обновляем UI кнопки
|
||
updateLikeButton(btn, liked);
|
||
|
||
// Обновляем счетчик
|
||
updateLikeCount(btn, count);
|
||
|
||
// Поддержка серверного постбэка (для форм)
|
||
updateFormOpInput(btn, liked);
|
||
|
||
} catch (err) {
|
||
console.error('likeHandler error:', err);
|
||
} finally {
|
||
btn.disabled = false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Обновить визуальное состояние кнопки лайка
|
||
* @param {HTMLElement} btn - Кнопка лайка
|
||
* @param {boolean} liked - Новое состояние (лайкнуто/не лайкнуто)
|
||
*/
|
||
function updateLikeButton(btn, liked) {
|
||
btn.classList.toggle('is-liked', liked);
|
||
btn.setAttribute('aria-pressed', liked ? 'true' : 'false');
|
||
|
||
// Переключаем иконку (пустое/заполненное сердце)
|
||
const icon = btn.querySelector('.bi');
|
||
if (icon) {
|
||
icon.classList.toggle('bi-heart', !liked);
|
||
icon.classList.toggle('bi-heart-fill', liked);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Обновить счетчик лайков
|
||
* @param {HTMLElement} btn - Кнопка лайка
|
||
* @param {number|null} count - Новое количество лайков
|
||
*/
|
||
function updateLikeCount(btn, count) {
|
||
if (count === null) return;
|
||
|
||
const card = btn.closest('[data-post-card]');
|
||
const cnt = card?.querySelector('[data-like-count]');
|
||
if (cnt) {
|
||
cnt.textContent = String(count);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Обновить скрытый input для серверного постбэка
|
||
* @param {HTMLElement} btn - Кнопка лайка
|
||
* @param {boolean} liked - Текущее состояние
|
||
*/
|
||
function updateFormOpInput(btn, liked) {
|
||
const form = btn.closest('form');
|
||
const opInput = form?.querySelector('input[name="op"]');
|
||
if (opInput) {
|
||
opInput.value = liked ? 'unlike' : 'like';
|
||
}
|
||
}
|