/** * Chat Form Manager * Управляет состоянием формы отправки сообщений * - Обычная отправка * - Редактирование сообщения * - Ответ на сообщение */ export function initChatFormManager(options = {}) { const { formId = 'chatForm', textareaId = 'id_content', chatId, uploadUrl = '/api/v1/communications/messages/upload/', editUrlTemplate = '/api/v1/communications/messages/{id}/', } = options; const form = document.getElementById(formId); const textarea = document.getElementById(textareaId); if (!form || !textarea) { console.warn('[ChatFormManager] Form or textarea not found'); return null; } // Состояние формы const state = { mode: 'send', // 'send' | 'edit' | 'reply' editMessageId: null, replyToMessageId: null, existingAttachments: [], // Вложения при редактировании originalAction: form.action, originalMethod: form.method, }; // ==================== API ==================== /** * Переключить форму в режим обычной отправки */ function setModeToSend() { state.mode = 'send'; state.editMessageId = null; state.replyToMessageId = null; // Восстанавливаем action form.action = uploadUrl; form.method = 'POST'; // Удаляем hidden поля removeHiddenInput('message_id'); removeHiddenInput('reply_to'); // Удаляем индикаторы removeEditIndicator(); removeReplyIndicator(); // Очищаем текст textarea.value = ''; textarea.placeholder = 'Напишите сообщение…'; textarea.focus(); console.log('[ChatFormManager] Mode: SEND'); } /** * Переключить форму в режим редактирования * @param {number|string} messageId - ID сообщения * @param {string} currentContent - Текущий текст сообщения * @param {Array} existingAttachments - Существующие вложения [{id, file_url, filename, ...}] */ function setModeToEdit(messageId, currentContent = '', existingAttachments = []) { console.log('[ChatFormManager] setModeToEdit called with:', { messageId, contentLength: currentContent?.length || 0, attachmentsCount: existingAttachments?.length || 0, attachments: existingAttachments }); if (!messageId) { console.error('[ChatFormManager] messageId required for edit mode'); return; } state.mode = 'edit'; state.editMessageId = messageId; state.replyToMessageId = null; state.existingAttachments = existingAttachments; // Сохраняем существующие файлы // Меняем action на API редактирования const editUrl = editUrlTemplate.replace('{id}', messageId); form.action = editUrl; form.method = 'POST'; // Добавляем hidden input для message_id (на всякий случай) addHiddenInput('message_id', messageId); // Удаляем reply_to если был removeHiddenInput('reply_to'); // Удаляем индикатор ответа removeReplyIndicator(); // ВАЖНО: Очищаем новые загруженные файлы (input type="file") clearFileInputs(); // Показываем индикатор редактирования showEditIndicator(messageId, currentContent); // Добавляем существующие вложения в chatComposer if (existingAttachments && existingAttachments.length > 0 && window.chatComposer) { console.log('[ChatFormManager] Adding existing attachments to chatComposer:', existingAttachments); // Сохраняем оригинальный метод removeFile if (!window.chatComposer._originalRemoveFile) { window.chatComposer._originalRemoveFile = window.chatComposer.removeFile.bind(window.chatComposer); } // Переопределяем removeFile чтобы учитывать существующие файлы window.chatComposer.removeFile = function(entryId) { console.log('[ChatComposer] removeFile called:', entryId); // Если это существующий файл - удаляем через chatFormManager if (entryId.startsWith('existing-')) { const attachmentId = entryId.replace('existing-', ''); const manager = window.chatFormManager; if (manager && manager.state) { manager.state.existingAttachments = manager.state.existingAttachments.filter( att => att.id != attachmentId ); console.log('[ChatComposer] Removed existing attachment:', attachmentId); } } // Вызываем оригинальный метод this._originalRemoveFile(entryId); }; existingAttachments.forEach(att => { // Создаем фейковый File объект для существующих вложений // chatComposer будет отображать их как обычные файлы const mockFile = createMockFileFromAttachment(att); if (mockFile) { const fileId = `existing-${att.id}`; window.chatComposer.selectedFiles.push({ id: fileId, file: mockFile, existingId: att.id }); } }); window.chatComposer.renderPreview(); } // Заполняем textarea текущим содержимым textarea.value = currentContent || ''; textarea.placeholder = 'Редактирование сообщения…'; textarea.focus(); // Курсор в конец textarea.setSelectionRange(textarea.value.length, textarea.value.length); console.log('[ChatFormManager] Mode: EDIT', { messageId, contentLength: currentContent?.length || 0, attachmentsCount: existingAttachments?.length || 0 }); } /** * Переключить форму в режим ответа */ function setModeToReply(messageId, authorName, messagePreview) { if (!messageId) { console.error('[ChatFormManager] messageId required for reply mode'); return; } // Если было редактирование - сбрасываем if (state.mode === 'edit') { form.action = uploadUrl; form.method = 'POST'; removeHiddenInput('message_id'); removeEditIndicator(); } state.mode = 'reply'; state.editMessageId = null; state.replyToMessageId = messageId; // Action остаётся обычным (upload-message) form.action = uploadUrl; form.method = 'POST'; // Добавляем hidden input reply_to addHiddenInput('reply_to', messageId); // Показываем индикатор ответа showReplyIndicator(messageId, authorName, messagePreview); // Фокус на textarea textarea.placeholder = `Ответ на сообщение от ${authorName}…`; textarea.focus(); console.log('[ChatFormManager] Mode: REPLY', messageId); } /** * Отменить текущее действие (вернуться к обычной отправке) */ function cancel() { setModeToSend(); } /** * Получить текущий режим */ function getMode() { return state.mode; } /** * Получить данные для отправки */ function getFormData() { return { mode: state.mode, editMessageId: state.editMessageId, replyToMessageId: state.replyToMessageId, content: textarea.value.trim(), }; } // ==================== HELPER FUNCTIONS ==================== function addHiddenInput(name, value) { // Удаляем если уже есть removeHiddenInput(name); const input = document.createElement('input'); input.type = 'hidden'; input.name = name; input.value = value; input.dataset.chatFormManager = '1'; form.appendChild(input); } function removeHiddenInput(name) { const existing = form.querySelector(`input[name="${name}"][data-chat-form-manager="1"]`); if (existing) { existing.remove(); } } /** * Создает mock File объект из данных существующего вложения */ function createMockFileFromAttachment(att) { const fileName = att.filename || att.file_name || 'Файл'; const fileUrl = att.file_url || att.url || ''; if (!fileUrl) return null; // Определяем MIME type из URL let mimeType = 'application/octet-stream'; if (fileUrl.match(/\.(jpg|jpeg)$/i)) mimeType = 'image/jpeg'; else if (fileUrl.match(/\.png$/i)) mimeType = 'image/png'; else if (fileUrl.match(/\.gif$/i)) mimeType = 'image/gif'; else if (fileUrl.match(/\.webp$/i)) mimeType = 'image/webp'; else if (fileUrl.match(/\.mp4$/i)) mimeType = 'video/mp4'; else if (fileUrl.match(/\.pdf$/i)) mimeType = 'application/pdf'; // Создаем mock File объект с расширенными свойствами const mockFile = { name: fileName, type: mimeType, size: 0, // Размер неизвестен для существующих файлов lastModified: Date.now(), // Дополнительные свойства для идентификации _isExisting: true, _existingId: att.id, _existingUrl: fileUrl, _sizeStr: att.size_str || '' }; return mockFile; } function showEditIndicator(messageId, content) { console.log('[ChatFormManager] showEditIndicator called:', { messageId, contentLength: content?.length || 0 }); removeEditIndicator(); const preview = content.length > 50 ? content.substring(0, 50) + '…' : content; const indicator = document.createElement('div'); indicator.className = 'edit-indicator alert alert-info d-flex align-items-center justify-content-between py-2 px-3 mb-2'; indicator.dataset.editIndicator = '1'; indicator.innerHTML = `