/** * @fileoverview Chat Message Templates - HTML шаблоны для рендеринга сообщений чата * @module components/chatMessageTemplates * * РЕФАКТОРИНГ: Теперь использует MessageRenderer как единый источник правды */ import { esc } from '../utils/stringUtils.js'; import { MessageRenderer } from './messageRenderer.js'; /** * Дополняет число нулём слева * @param {number} n - Число * @returns {string} Строка с ведущим нулём */ const z2 = n => (n < 10 ? '0' : '') + n; /** * Извлекает timestamp из сообщения * @param {Object} msg - Объект сообщения * @returns {number} Unix timestamp в миллисекундах */ export function toTimestamp(msg) { if (msg && msg.created_ts != null && !isNaN(Number(msg.created_ts))) { return Number(msg.created_ts); } if (msg && msg.created) { const t = Date.parse(msg.created); if (!isNaN(t)) return t; } return Date.now(); } /** * Форматирует timestamp в HH:MM * @param {number} ts - Unix timestamp * @returns {string} Время в формате HH:MM */ export function formatTime(ts) { const d = new Date(ts); return `${z2(d.getHours())}:${z2(d.getMinutes())}`; } /** * Форматирует timestamp в DD.MM.YYYY * @param {number} ts - Unix timestamp * @returns {string} Дата в формате DD.MM.YYYY */ export function formatDay(ts) { const d = new Date(ts); return `${z2(d.getDate())}.${z2(d.getMonth() + 1)}.${d.getFullYear()}`; } /** * Создаёт HTML для аватара * @param {string} url - URL аватара * @returns {string} HTML аватара */ export function avatarHTML(url) { return url ? `` : ``; } /** * Создаёт HTML для имени пользователя с ссылкой * @param {number|string} userId - ID пользователя * @param {string} name - Имя пользователя * @param {string} url - URL профиля * @param {number|string} meId - ID текущего пользователя * @param {string} profileUrl - URL профиля текущего пользователя * @param {string} detailUrlTemplate - Шаблон URL деталей сотрудника * @returns {string} HTML имени с ссылкой */ export function nameHTML(userId, name, url, meId, profileUrl, detailUrlTemplate) { if (Number(userId) === Number(meId)) { return `Вы`; } if (url) { return `${esc(name || 'Сотрудник')}`; } const detailUrl = detailUrlTemplate.replace('/0/', `/${userId}/`); return `${esc(name || 'Сотрудник')}`; } /** * Форматирует размер файла * @param {number} bytes - Размер в байтах * @returns {string} Размер в человекочитаемом формате */ export function formatFileSize(bytes) { if (bytes < 1024) return bytes + ' B'; if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB'; return (bytes / (1024 * 1024)).toFixed(1) + ' MB'; } /** * Создаёт разделитель дня * @param {string} dateStr - Дата в текстовом формате * @returns {HTMLElement} DOM элемент разделителя */ export function createDayDivider(dateStr) { const div = document.createElement('div'); div.className = 'day-divider'; div.innerHTML = `${esc(dateStr)}`; return div; } /** * Создаёт элемент сообщения * @param {Object} msg - Объект сообщения * @param {number|string} msg.id - ID сообщения * @param {number|string} msg.author_id - ID автора * @param {string} msg.author_name - Имя автора * @param {string} msg.author - Альтернативное имя автора * @param {string} msg.author_url - URL профиля автора * @param {string} msg.avatar - URL аватара автора * @param {string} msg.content - Текст сообщения * @param {Object} options - Опции рендеринга * @param {number|string} options.meId - ID текущего пользователя * @param {string} options.profileUrl - URL профиля текущего пользователя * @param {string} options.detailUrlTemplate - Шаблон URL деталей сотрудника * @param {Object.} options.avatarMap - Карта аватаров * @returns {HTMLElement} DOM элемент сообщения */ export function createMessageElement(msg, options = {}) { const { meId, profileUrl = '/employees/profile/', detailUrlTemplate = '/employees/0/', avatarMap = {} } = options; // Используем MessageRenderer для единообразия const renderer = new MessageRenderer({ meId, profileUrl, detailUrlTemplate, currentUserAvatar: avatarMap[meId] || '' }); // Обогащаем сообщение аватаром из карты const enrichedMsg = { ...msg, avatar: msg.avatar || avatarMap[msg.author_id] || '' }; const mine = Number(msg.author_id) === Number(meId); const isPending = Boolean(msg.is_pending); // Создание обёртки const wrap = document.createElement('div'); wrap.className = `d-flex mb-3 msg ${mine ? '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 = toTimestamp(msg); 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 из MessageRenderer для единообразного рендеринга const htmlContent = renderer.buildMessageInnerHtml(enrichedMsg, mine); // Добавляем статус отправки для 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 = 'Отправляем…'; bubble.appendChild(pendingStatus); } wrap.innerHTML = tempDiv.innerHTML; } else { wrap.innerHTML = htmlContent; } return wrap; }