Corp/backend/static/js/components/messageEditing.js
Игорь Надеин fa0948c159 feat(communications): добавлена поддержка редактирования сообщений с изменением attachments
Изменения:
- Новый 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 с задачей очистки
2026-01-15 11:13:54 +03:00

198 lines
8.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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');
}