refactor(messenger): Phase 2 - унификация рендеринга сообщений
- Добавлен метод MessageRenderer.createMessageElement() для создания DOM элементов - Обновлен chatComposer для использования MessageRenderer - Обновлен chatHistoryLoader для использования MessageRenderer - Обновлен chatDetail.js для передачи messageRenderer в модули - Все модули теперь используют единый источник рендеринга - Сохранена обратная совместимость через fallback на createMessageElement() Phase 2 of 4: Rendering unification completed
This commit is contained in:
parent
5906bbaf86
commit
845c59311c
@ -3,6 +3,8 @@ import { createMessageElement } from './chatMessageTemplates.js';
|
||||
/**
|
||||
* Chat Composer module
|
||||
* Отвечает за ввод текста, прикрепление файлов и отправку сообщений через REST API.
|
||||
*
|
||||
* РЕФАКТОРИНГ: Теперь использует MessageRenderer для создания pending сообщений
|
||||
*/
|
||||
|
||||
const DEFAULT_CONFIG = {
|
||||
@ -49,6 +51,7 @@ class ChatComposer {
|
||||
this.meAvatar = options.meAvatar || form.dataset.meAvatar || this.scrollEl?.dataset.meAvatar || '';
|
||||
this.profileUrl = options.profileUrl || form.dataset.profileUrl || this.options.profileUrl;
|
||||
this.detailUrlTemplate = options.detailUrlTemplate || form.dataset.detailUrlTemplate || this.options.detailUrlTemplate;
|
||||
this.messageRenderer = options.messageRenderer || null; // РЕФАКТОРИНГ: Принимаем MessageRenderer
|
||||
this.selectedFiles = [];
|
||||
this.typingThrottle = 1200;
|
||||
this.lastTypingSent = 0;
|
||||
@ -544,11 +547,19 @@ class ChatComposer {
|
||||
is_pending: true
|
||||
};
|
||||
|
||||
const element = createMessageElement(msg, {
|
||||
meId: this.meId,
|
||||
profileUrl: this.profileUrl,
|
||||
detailUrlTemplate: this.detailUrlTemplate
|
||||
});
|
||||
// РЕФАКТОРИНГ: Используем MessageRenderer если доступен, иначе fallback на createMessageElement
|
||||
let element;
|
||||
if (this.messageRenderer) {
|
||||
element = this.messageRenderer.createMessageElement(msg, true); // true = isPending
|
||||
} else {
|
||||
// Fallback для обратной совместимости
|
||||
element = createMessageElement(msg, {
|
||||
meId: this.meId,
|
||||
profileUrl: this.profileUrl,
|
||||
detailUrlTemplate: this.detailUrlTemplate
|
||||
});
|
||||
}
|
||||
|
||||
element.dataset.pendingId = pendingId;
|
||||
this.scrollEl.appendChild(element);
|
||||
this.scrollEl.scrollTop = this.scrollEl.scrollHeight;
|
||||
|
||||
@ -5,6 +5,12 @@ import {
|
||||
toTimestamp
|
||||
} from './chatMessageTemplates.js';
|
||||
|
||||
/**
|
||||
* Chat History Loader - загрузка ранних сообщений при прокрутке вверх
|
||||
*
|
||||
* РЕФАКТОРИНГ: Теперь использует MessageRenderer для создания элементов сообщений
|
||||
*/
|
||||
|
||||
const DEFAULT_CONFIG = {
|
||||
scrollSelector: '#chatScroll',
|
||||
loaderSelector: '[data-history-loader]',
|
||||
@ -67,6 +73,7 @@ export function initChatHistoryLoader(options = {}) {
|
||||
const profileUrl = scrollEl.dataset.profileUrl || '/employees/profile/';
|
||||
const detailUrlTemplate = scrollEl.dataset.detailUrlTemplate || '/employees/0/';
|
||||
const avatarMap = getAvatarMapSnapshot();
|
||||
const messageRenderer = options.messageRenderer || null; // РЕФАКТОРИНГ: Принимаем MessageRenderer
|
||||
|
||||
const state = {
|
||||
loading: false,
|
||||
@ -113,6 +120,7 @@ export function initChatHistoryLoader(options = {}) {
|
||||
profileUrl,
|
||||
detailUrlTemplate,
|
||||
avatarMap,
|
||||
messageRenderer,
|
||||
state
|
||||
} = ctx;
|
||||
|
||||
@ -129,12 +137,20 @@ export function initChatHistoryLoader(options = {}) {
|
||||
}
|
||||
prevDay = day;
|
||||
|
||||
const element = createMessageElement(msg, {
|
||||
meId,
|
||||
profileUrl,
|
||||
detailUrlTemplate,
|
||||
avatarMap
|
||||
});
|
||||
// РЕФАКТОРИНГ: Используем MessageRenderer если доступен
|
||||
let element;
|
||||
if (messageRenderer) {
|
||||
element = messageRenderer.createMessageElement(msg, false);
|
||||
} else {
|
||||
// Fallback для обратной совместимости
|
||||
element = createMessageElement(msg, {
|
||||
meId,
|
||||
profileUrl,
|
||||
detailUrlTemplate,
|
||||
avatarMap
|
||||
});
|
||||
}
|
||||
|
||||
element.classList.remove('message-pending', 'message-pending--resolved');
|
||||
fragment.appendChild(element);
|
||||
});
|
||||
@ -186,6 +202,7 @@ export function initChatHistoryLoader(options = {}) {
|
||||
profileUrl,
|
||||
detailUrlTemplate,
|
||||
avatarMap,
|
||||
messageRenderer,
|
||||
state
|
||||
});
|
||||
state.oldestId = String(payload.messages[0].id);
|
||||
|
||||
@ -120,6 +120,60 @@ export class MessageRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Создаёт DOM элемент сообщения без вставки в контейнер
|
||||
* Используется для pending сообщений, где нужен прямой доступ к элементу
|
||||
* @param {Object} msg - Объект сообщения
|
||||
* @param {boolean} isPending - Является ли сообщение pending
|
||||
* @returns {HTMLElement} DOM элемент сообщения
|
||||
*/
|
||||
createMessageElement(msg, isPending = false) {
|
||||
const isOwn = msg.author_id === this.currentUserId;
|
||||
|
||||
const wrap = document.createElement('div');
|
||||
wrap.className = `d-flex mb-3 msg ${isOwn ? 'justify-content-end' : 'justify-content-start'}`;
|
||||
|
||||
if (isPending) {
|
||||
wrap.classList.add('message-pending');
|
||||
}
|
||||
|
||||
if (msg.id != null) {
|
||||
wrap.setAttribute('data-id', String(msg.id));
|
||||
wrap.setAttribute('data-message-id', String(msg.id));
|
||||
const reactionsData = msg.reactions_summary || {};
|
||||
wrap.setAttribute('data-reactions', JSON.stringify(reactionsData));
|
||||
}
|
||||
|
||||
const ts = msg.created_ts || Date.now();
|
||||
wrap.setAttribute('data-ts', String(ts));
|
||||
wrap.setAttribute('data-author-id', String(msg.author_id || ''));
|
||||
wrap.setAttribute('data-is-edited', String(msg.is_edited || false));
|
||||
wrap.setAttribute('data-edited-at', String(msg.edited_at || ''));
|
||||
|
||||
// Используем buildMessageInnerHtml для единообразного рендеринга
|
||||
const htmlContent = this.buildMessageInnerHtml(msg, isOwn);
|
||||
|
||||
// Добавляем статус отправки для pending сообщений
|
||||
if (isPending) {
|
||||
const tempDiv = document.createElement('div');
|
||||
tempDiv.innerHTML = htmlContent;
|
||||
|
||||
const bubble = tempDiv.querySelector('.bubble');
|
||||
if (bubble) {
|
||||
const pendingStatus = document.createElement('div');
|
||||
pendingStatus.className = 'message-status small text-secondary mt-2';
|
||||
pendingStatus.innerHTML = '<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>Отправляем…';
|
||||
bubble.appendChild(pendingStatus);
|
||||
}
|
||||
|
||||
wrap.innerHTML = tempDiv.innerHTML;
|
||||
} else {
|
||||
wrap.innerHTML = htmlContent;
|
||||
}
|
||||
|
||||
return wrap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Строит HTML одного сообщения
|
||||
*/
|
||||
|
||||
@ -146,6 +146,19 @@ function initializeComponents(config, userWs) {
|
||||
|
||||
console.log('[ChatDetail] MarkRead initialized');
|
||||
|
||||
// ========== MESSAGE RENDERER ==========
|
||||
console.log('[ChatDetail] Initializing MessageRenderer...');
|
||||
|
||||
const messageRenderer = new MessageRenderer({
|
||||
containerId: 'chatScroll',
|
||||
currentUserId: config.userId,
|
||||
currentUserAvatar: config.userAvatar || '',
|
||||
profileUrl: config.profileUrl || '/employees/profile/',
|
||||
detailUrlTemplate: config.detailUrlTemplate || '/employees/detail/0/'
|
||||
});
|
||||
|
||||
console.log('[ChatDetail] MessageRenderer initialized');
|
||||
|
||||
// ========== COMPOSER ==========
|
||||
console.log('[ChatDetail] Initializing Composer...');
|
||||
const composer = initChatComposer({
|
||||
@ -155,7 +168,8 @@ function initializeComponents(config, userWs) {
|
||||
uploadUrl: config.uploadUrl,
|
||||
meId: config.userId,
|
||||
meName: config.userName,
|
||||
meAvatar: config.userAvatar
|
||||
meAvatar: config.userAvatar,
|
||||
messageRenderer: messageRenderer // Передаём MessageRenderer
|
||||
});
|
||||
|
||||
if (!composer) {
|
||||
@ -176,24 +190,12 @@ function initializeComponents(config, userWs) {
|
||||
scrollContainerId: 'chatScroll',
|
||||
fetchUrl: config.messagesUrl,
|
||||
avatarMap: avatarMap,
|
||||
meId: config.userId
|
||||
meId: config.userId,
|
||||
messageRenderer: messageRenderer // Передаём MessageRenderer
|
||||
});
|
||||
|
||||
console.log('[ChatDetail] HistoryLoader initialized');
|
||||
|
||||
// ========== MESSAGE RENDERER ==========
|
||||
console.log('[ChatDetail] Initializing MessageRenderer...');
|
||||
|
||||
const messageRenderer = new MessageRenderer({
|
||||
containerId: 'chatScroll',
|
||||
currentUserId: config.userId,
|
||||
currentUserAvatar: config.userAvatar || '',
|
||||
profileUrl: config.profileUrl || '/employees/profile/',
|
||||
detailUrlTemplate: config.detailUrlTemplate || '/employees/detail/0/'
|
||||
});
|
||||
|
||||
console.log('[ChatDetail] MessageRenderer initialized');
|
||||
|
||||
// ========== CONTEXT MENU ==========
|
||||
// Инициализация контекстного меню перенесена в chat-detail-enhanced.js
|
||||
// для избежания конфликтов и дублирования
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user