# Рефакторинг системы рендеринга сообщений
## Дата: 4 декабря 2025 г.
## Проблема
Система рендеринга сообщений имела дублирование кода:
- `messageRenderer.js` - рендерил новые сообщения через WebSocket
- `chatMessageTemplates.js` - рендерил старые сообщения при скролле вверх
Оба файла содержали:
1. **Многострочные HTML-шаблоны** с переносами строк и отступами
2. **Дублирующуюся логику** для формирования HTML
3. **Разные подходы** к обработке данных
### Симптомы
- Пилюли сообщений растягивались по высоте из-за whitespace в HTML
- `white-space: pre-line` сохранял переносы из исходного кода JavaScript
- Различное отображение сообщений в зависимости от способа загрузки
## Решение
### 1. Единый источник правды
**messageRenderer.js** стал единственным местом генерации HTML:
```javascript
export class MessageRenderer {
// Полный HTML с обёрткой .msg для WebSocket
buildMessageHtml(msg, isOwn) {
return `
${this.buildMessageInnerHtml(msg, isOwn)}
`;
}
// Внутренний HTML без обёртки для DOM-элементов
buildMessageInnerHtml(msg, isOwn) {
// Все шаблоны в одну строку без переносов
return `${avatarHtml}...
${rightAvatarHtml}`;
}
}
```
### 2. Обёртка в chatMessageTemplates.js
**chatMessageTemplates.js** теперь использует MessageRenderer:
```javascript
import { MessageRenderer } from './messageRenderer.js';
export function createMessageElement(msg, options = {}) {
const renderer = new MessageRenderer({
meId: options.meId,
profileUrl: options.profileUrl,
detailUrlTemplate: options.detailUrlTemplate,
currentUserAvatar: options.avatarMap[options.meId]
});
const wrap = document.createElement('div');
// Настройка атрибутов wrap...
// Используем единый рендерер
wrap.innerHTML = renderer.buildMessageInnerHtml(enrichedMsg, mine);
return wrap;
}
```
### 3. Устранение многострочных шаблонов
**ДО:**
```javascript
const forwardedHtml = msg.is_forwarded ? `
` : '';
```
**ПОСЛЕ:**
```javascript
const forwardedHtml = msg.is_forwarded ? `Переслано от ${msg.forwarded_from.author_name || 'Пользователя'}${msg.forwarded_from.chat_name ? ` из «${msg.forwarded_from.chat_name}»` : ''}
${msg.forwarded_from.created_at ? `
${msg.forwarded_from.created_at}
` : ''}
` : '';
```
### 4. Рефакторинг всех компонентов
Применено ко всем HTML-генераторам:
- ✅ `forwardedHtml` - информация о пересылке
- ✅ `replyHtml` - цитата ответа
- ✅ `avatarHtml` - аватары пользователей
- ✅ `rightAvatarHtml` - аватар справа (свои сообщения)
- ✅ `attachmentsHtml` - вложения (изображения, файлы)
- ✅ `pollHtml` - голосования/опросы
- ✅ `bubble` - основной контейнер сообщения
## Архитектура после рефакторинга
```
┌──────────────────────────────────────────────────┐
│ messageRenderer.js │
│ │
│ ┌─────────────────────────────────────────┐ │
│ │ buildMessageHtml(msg, isOwn) │ │
│ │ → Полный HTML с обёрткой .msg │ │
│ │ → Используется для WebSocket │ │
│ └─────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────┐ │
│ │ buildMessageInnerHtml(msg, isOwn) │◄───┼─── Единый источник
│ │ → HTML без обёртки .msg │ │ правды для всех
│ │ → Все шаблоны в одну строку │ │ HTML-шаблонов
│ │ → Нет whitespace из исходного кода │ │
│ └─────────────────────────────────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ buildAttachmentHtml buildPollHtml ... │
└──────────────────────────────────────────────────┘
▲
│
│ import & использование
│
┌──────────────────────────────────────────────────┐
│ chatMessageTemplates.js │
│ │
│ createMessageElement(msg, options) │
│ → Создаёт DOM элемент │
│ → Использует MessageRenderer.buildMessageInnerHtml │
│ → Добавляет pending статус если нужно │
└──────────────────────────────────────────────────┘
▲
│
│ использование
│
┌──────────────────────────────────────────────────┐
│ chatHistoryLoader.js │
│ │
│ prependMessages() → createMessageElement() │
│ → Загрузка старых сообщений при скролле вверх │
└──────────────────────────────────────────────────┘
```
## Поток данных
### WebSocket (новые сообщения)
```
WebSocket event
↓
chatDetail.js
↓
messageRenderer.buildMessageHtml()
↓
insertAdjacentHTML()
↓
DOM
```
### HTTP + Скролл (старые сообщения)
```
Scroll вверх
↓
chatHistoryLoader.loadMore()
↓
fetch(/api/messages?before_id=...)
↓
chatMessageTemplates.createMessageElement()
↓
→ MessageRenderer.buildMessageInnerHtml()
↓
DOM element
↓
insertBefore()
```
## Преимущества рефакторинга
### ✅ Единообразие
- Одинаковый HTML независимо от способа загрузки
- Нет расхождений между WebSocket и HTTP
- Единая логика обработки всех компонентов (ответы, вложения, опросы)
### ✅ Производительность
- Нет лишних whitespace в HTML
- Компактные пилюли сообщений
- Правильная работа `white-space: pre-line` (сохраняет только `\n` из контента)
### ✅ Поддерживаемость
- DRY (Don't Repeat Yourself)
- Одно место для изменений
- Легче тестировать и дебажить
### ✅ Расширяемость
- Новые типы вложений добавляются в одном месте
- Новые поля сообщений обрабатываются единообразно
## CSS изменения
### _chat-detail.scss
```scss
.bubble {
border-radius: 16px;
padding: var(--space-1) var(--space-2);
word-break: break-word;
white-space: pre-line; /* Сохраняет \n из контента, схлопывает пробелы из HTML */
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
> div {
white-space: inherit; /* Наследование для вложенных элементов */
}
}
```
## Тестирование
После рефакторинга проверить:
1. ✅ Переносы строк (`\n`) отображаются корректно
2. ✅ Высота пилюль соответствует контенту (без растягивания)
3. ✅ Сообщения с 1 символом компактные
4. ✅ Старые сообщения (скролл вверх) выглядят так же как новые (WebSocket)
5. ✅ Сообщения с вложениями, опросами, ответами не имеют лишних отступов
6. ✅ Аватары отображаются корректно
7. ✅ Реакции работают
8. ✅ Редактирование сообщений работает
## Совместимость
### Обратная совместимость
- ✅ Все существующие функции сохранены
- ✅ API не изменился
- ✅ DOM-структура осталась прежней
- ✅ CSS-классы не изменились
### Зависимости
```javascript
// messageRenderer.js
export class MessageRenderer { ... }
export function createMessageRenderer(config) { ... }
// chatMessageTemplates.js
import { MessageRenderer } from './messageRenderer.js';
import { esc } from '../utils/stringUtils.js';
// chatHistoryLoader.js
import { createMessageElement } from './chatMessageTemplates.js';
```
## Следующие шаги
1. **Тестирование** - проверить все сценарии использования
2. **Мониторинг** - отследить производительность в production
3. **Документация** - обновить комментарии в коде
4. **Оптимизация** - возможно кеширование MessageRenderer
5. **Расширение** - добавить поддержку новых типов контента
## Файлы изменены
- ✅ `backend/static/js/components/messageRenderer.js`
- Добавлен метод `buildMessageInnerHtml()`
- Все шаблоны переведены в одну строку
- Добавлена поддержка параметра `meId`
- ✅ `backend/static/js/components/chatMessageTemplates.js`
- Переписан `createMessageElement()` для использования MessageRenderer
- Удалён дублирующийся код генерации HTML
- Сохранена обратная совместимость API
- ✅ `backend/static/scss/components/_chat-detail.scss`
- Изменён `white-space: pre-wrap` → `pre-line`
- Добавлено правило наследования для вложенных `div`
## Авторы
- Рефакторинг: GitHub Copilot + Igor
- Дата: 4 декабря 2025 г.