Изменения: - Новый endpoint /api/v1/communications/messages/upload-temp/ для загрузки файлов без создания сообщения - Поле message в MessageAttachment теперь nullable для временных вложений - Логика перемещения attachments между сообщениями при редактировании - Frontend использует upload-temp вместо создания временных сообщений - Celery задача cleanup_orphaned_attachments для очистки забытых файлов (каждый час) - Исправлена проблема с WebSocket обновлениями при редактировании - Удалены debug логи после успешного тестирования Технические детали: - communications/models.py: добавлено null=True, blank=True к MessageAttachment.message - communications/migrations/0022: миграция для изменения поля - api/v1/communications/viewsets.py: новый action upload_temp() + логика перемещения в update() - static/js/components/chatComposer.js: использование upload-temp вместо upload - communications/tasks.py: новая Celery задача для очистки старых orphaned attachments - eusrr_backend/settings.py: добавлен CELERY_BEAT_SCHEDULE с задачей очистки
198 lines
8.0 KiB
JavaScript
198 lines
8.0 KiB
JavaScript
/**
|
||
* messageEditing.js
|
||
*
|
||
* Обработка событий редактирования сообщений через WebSocket.
|
||
* Обновляет DOM когда сообщение было отредактировано другим пользователем
|
||
* или в другой вкладке.
|
||
*/
|
||
|
||
import { MessageRendererV2 } from '../renderers/messageRendererV2.js';
|
||
|
||
/**
|
||
* Инициализация обработки редактирования сообщений
|
||
*/
|
||
export function initMessageEditing() {
|
||
console.log('[MessageEditing] Initializing message editing handler...');
|
||
|
||
// Слушаем событие редактирования от WebSocket
|
||
window.addEventListener('chat:message-edited', handleMessageEdited);
|
||
|
||
console.log('[MessageEditing] Handler initialized successfully');
|
||
}
|
||
|
||
/**
|
||
* Обработчик события редактирования сообщения
|
||
* @param {CustomEvent} event - событие с данными отредактированного сообщения
|
||
*/
|
||
function handleMessageEdited(event) {
|
||
const data = event.detail;
|
||
console.log('[MessageEditing] Message edited event received:', data);
|
||
|
||
const message = data.payload || data.message;
|
||
if (!message || !message.id) {
|
||
console.warn('[MessageEditing] Invalid message data:', data);
|
||
return;
|
||
}
|
||
|
||
// Подробный лог того что пришло
|
||
console.log('[MessageEditing] ========== MESSAGE DATA ==========');
|
||
console.log('[MessageEditing] ID:', message.id);
|
||
console.log('[MessageEditing] Content:', message.content);
|
||
console.log('[MessageEditing] has_attachments:', message.has_attachments);
|
||
console.log('[MessageEditing] attachments:', message.attachments);
|
||
console.log('[MessageEditing] attachments length:', message.attachments?.length);
|
||
console.log('[MessageEditing] reply_to:', message.reply_to);
|
||
console.log('[MessageEditing] poll:', message.poll);
|
||
console.log('[MessageEditing] is_forwarded:', message.is_forwarded);
|
||
console.log('[MessageEditing] forwarded_from:', message.forwarded_from);
|
||
console.log('[MessageEditing] reactions_summary:', message.reactions_summary);
|
||
console.log('[MessageEditing] is_edited:', message.is_edited);
|
||
console.log('[MessageEditing] ===================================');
|
||
|
||
updateMessageInDOM(message);
|
||
}
|
||
|
||
/**
|
||
* Обновляет сообщение в DOM после редактирования
|
||
* ИСПОЛЬЗУЕТ ПОЛНУЮ ПЕРЕРИСОВКУ через MessageRenderer
|
||
* @param {Object} message - данные сообщения
|
||
*/
|
||
function updateMessageInDOM(message) {
|
||
// Ищем все элементы с этим message_id (может быть несколько копий)
|
||
const messageElements = document.querySelectorAll(`[data-message-id="${message.id}"]`);
|
||
|
||
if (messageElements.length === 0) {
|
||
console.log('[MessageEditing] Message element not found in DOM:', message.id);
|
||
return;
|
||
}
|
||
|
||
console.log('[MessageEditing] Updating message elements:', messageElements.length);
|
||
console.log('[MessageEditing] Message data:', {
|
||
id: message.id,
|
||
content: message.content,
|
||
has_attachments: message.has_attachments,
|
||
attachments: message.attachments?.length || 0,
|
||
reply_to: !!message.reply_to,
|
||
poll: !!message.poll,
|
||
reactions: Object.keys(message.reactions_summary || {}).length,
|
||
is_edited: message.is_edited
|
||
});
|
||
|
||
messageElements.forEach(oldMessageEl => {
|
||
try {
|
||
// Используем существующий renderer из ChatControllerV2
|
||
const renderer = window.chatController?.messageRenderer;
|
||
|
||
if (!renderer) {
|
||
console.error('[MessageEditing] MessageRenderer not available from ChatController');
|
||
return;
|
||
}
|
||
|
||
// Рендерим сообщение через существующий renderer (возвращает DOM элемент)
|
||
const newMessageEl = renderer.renderSingleMessage(message);
|
||
|
||
if (!newMessageEl) {
|
||
console.error('[MessageEditing] Failed to render new message element');
|
||
return;
|
||
}
|
||
|
||
console.log('[MessageEditing] Generated element:', {
|
||
tagName: newMessageEl.tagName,
|
||
className: newMessageEl.className,
|
||
hasContent: !!newMessageEl.querySelector('.message-content'),
|
||
hasAttachments: !!newMessageEl.querySelector('.message-attachments')
|
||
});
|
||
|
||
// Сохраняем позицию скролла
|
||
const scrollContainer = document.getElementById('chatScroll');
|
||
const shouldMaintainScroll = scrollContainer && (
|
||
scrollContainer.scrollHeight - scrollContainer.scrollTop - scrollContainer.clientHeight > 100
|
||
);
|
||
const scrollBefore = scrollContainer?.scrollTop || 0;
|
||
|
||
// Заменяем элемент
|
||
oldMessageEl.replaceWith(newMessageEl);
|
||
console.log('[MessageEditing] ✓ Message DOM replaced');
|
||
|
||
// Восстанавливаем скролл если пользователь не внизу
|
||
if (shouldMaintainScroll && scrollContainer) {
|
||
scrollContainer.scrollTop = scrollBefore;
|
||
}
|
||
|
||
// Переинициализируем компоненты для нового элемента
|
||
const currentUserId = renderer.currentUserId;
|
||
reinitMessageComponents(newMessageEl, message.id, currentUserId);
|
||
|
||
} catch (error) {
|
||
console.error('[MessageEditing] Error updating message:', error);
|
||
}
|
||
});
|
||
|
||
console.log('[MessageEditing] ✓ Message fully updated with all components:', message.id);
|
||
}
|
||
|
||
/**
|
||
* Переинициализирует компоненты сообщения после замены DOM
|
||
* @param {HTMLElement} messageEl - элемент сообщения
|
||
* @param {number} messageId - ID сообщения
|
||
* @param {number} currentUserId - ID текущего пользователя
|
||
*/
|
||
function reinitMessageComponents(messageEl, messageId, currentUserId) {
|
||
console.log('[MessageEditing] Reinitializing components for message:', messageId);
|
||
|
||
// 1. Реинициализируем реакции
|
||
if (window.reactions && typeof window.reactions.initMessageReactions === 'function') {
|
||
try {
|
||
window.reactions.initMessageReactions(messageEl, messageId, currentUserId);
|
||
console.log('[MessageEditing] ✓ Reactions reinitialized');
|
||
} catch (error) {
|
||
console.error('[MessageEditing] Failed to reinit reactions:', error);
|
||
}
|
||
}
|
||
|
||
// 2. Контекстное меню работает через делегирование событий,
|
||
// переинициализация не требуется
|
||
|
||
// 3. Lightbox для изображений (если используется)
|
||
const images = messageEl.querySelectorAll('img[data-lightbox], .attachment-image');
|
||
if (images.length > 0 && window.initLightbox) {
|
||
try {
|
||
window.initLightbox(messageEl);
|
||
console.log('[MessageEditing] ✓ Lightbox reinitialized for', images.length, 'images');
|
||
} catch (error) {
|
||
console.error('[MessageEditing] Failed to reinit lightbox:', error);
|
||
}
|
||
}
|
||
|
||
// 4. Голосования (если есть)
|
||
const pollEl = messageEl.querySelector('[data-poll-id]');
|
||
if (pollEl && window.chatPoll) {
|
||
try {
|
||
const pollId = pollEl.dataset.pollId;
|
||
console.log('[MessageEditing] Refreshing poll:', pollId);
|
||
// Вызываем refreshPoll для обновления состояния опроса
|
||
window.chatPoll.refreshPoll(pollId, pollEl);
|
||
console.log('[MessageEditing] ✓ Poll reinitialized, id:', pollId);
|
||
} catch (error) {
|
||
console.error('[MessageEditing] Failed to reinit poll:', error);
|
||
}
|
||
}
|
||
|
||
// 5. Обработчики кликов на reply-reference (скролл к сообщению)
|
||
const replyRef = messageEl.querySelector('.reply-reference');
|
||
if (replyRef) {
|
||
// Уже обработано через onclick в HTML, но можно добавить дополнительную логику
|
||
console.log('[MessageEditing] ✓ Reply reference detected');
|
||
}
|
||
|
||
console.log('[MessageEditing] ✓ All components reinitialized');
|
||
}
|
||
|
||
/**
|
||
* Очистка обработчика (для случаев когда нужно отключить)
|
||
*/
|
||
export function cleanupMessageEditing() {
|
||
window.removeEventListener('chat:message-edited', handleMessageEdited);
|
||
console.log('[MessageEditing] Handler cleaned up');
|
||
}
|