Corp/backend/static/js/utils/dateDividers.js
Игорь Надеин 0e2a08fb38 chore: Add remaining test files and utilities
Added:
- Test chat API script
- Date group CSS and JS utilities
- IntersectionObserver modules
- Bidirectional loading test files
- Autoscroll testing checklist
2026-01-13 15:02:29 +03:00

246 lines
9.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.

/**
* DateDividerManager - Централизованная утилита для управления date dividers в чате
*
* Основано на архитектуре Telegram Web:
* - Единый источник логики для дат
* - Поддержка группировки сообщений
* - Sticky positioning
*
* @module dateDividers
*/
import { formatDay } from './chatUtils.js';
import { escapeHtml } from './chatUtils.js';
/**
* Менеджер для создания и управления date dividers
*/
export class DateDividerManager {
/**
* Форматирует дату для отображения (re-export из chatUtils)
*
* @param {Date|string|number} date - Дата для форматирования
* @returns {string} Отформатированная дата ("Сегодня", "Вчера", "5 января 2026")
*
* @example
* DateDividerManager.formatDay(new Date()) // "Сегодня"
* DateDividerManager.formatDay('2026-01-05') // "5 января 2026"
*/
static formatDay(date) {
return formatDay(date);
}
/**
* Создает DOM элемент date divider (простой вариант)
* Используется для обратной совместимости с текущей версией
*
* @param {string} text - Текст даты для отображения
* @returns {HTMLDivElement} DOM элемент divider
*
* @example
* const divider = DateDividerManager.createDivider('Сегодня');
* container.appendChild(divider);
*/
static createDivider(text) {
const dividerEl = document.createElement('div');
dividerEl.className = 'day-divider text-center small text-muted my-3';
dividerEl.setAttribute('data-date-divider', text);
const bubble = document.createElement('span');
bubble.className = 'px-3 py-1 rounded-pill bg-light';
bubble.textContent = escapeHtml(text);
dividerEl.appendChild(bubble);
return dividerEl;
}
/**
* Создает sticky date header (как в Telegram Web)
* Используется для группировки сообщений по датам
*
* @param {string} text - Текст даты для отображения
* @param {boolean} interactive - Добавить ли класс interactive для кликабельности
* @returns {HTMLDivElement} DOM элемент sticky date
*
* @example
* const stickyDate = DateDividerManager.createStickyDate('Сегодня', true);
* dateGroup.appendChild(stickyDate);
*/
static createStickyDate(text, interactive = false) {
const stickyEl = document.createElement('div');
const classes = ['sticky-date'];
if (interactive) {
classes.push('interactive');
}
stickyEl.className = classes.join(' ');
stickyEl.setAttribute('data-date-text', text);
const span = document.createElement('span');
span.setAttribute('dir', 'auto');
span.textContent = escapeHtml(text);
stickyEl.appendChild(span);
return stickyEl;
}
/**
* Проверяет, нужно ли добавлять date divider между сообщениями
*
* @param {string} currentDay - Дата текущего сообщения (отформатированная)
* @param {string} previousDay - Дата предыдущего сообщения (отформатированная)
* @returns {boolean} true если нужно добавить divider
*
* @example
* const needsDivider = DateDividerManager.shouldAddDivider('Сегодня', 'Вчера');
* if (needsDivider) {
* container.appendChild(DateDividerManager.createDivider('Сегодня'));
* }
*/
static shouldAddDivider(currentDay, previousDay) {
// Если это первое сообщение или даты разные - добавляем divider
return !previousDay || currentDay !== previousDay;
}
/**
* Извлекает дату из сообщения (универсальный метод)
*
* @param {Object} message - Объект сообщения
* @returns {Date} Дата сообщения
*
* @example
* const date = DateDividerManager.getMessageDate(message);
* const formatted = DateDividerManager.formatDay(date);
*/
static getMessageDate(message) {
// Поддержка разных форматов timestamp
const timestamp = message.created_ts || message.timestamp || message.created_at;
if (!timestamp) {
console.warn('[DateDividerManager] Message without timestamp:', message);
return new Date();
}
// Если timestamp в секундах (10 цифр), конвертируем в миллисекунды
const ts = timestamp.toString().length === 10 ? timestamp * 1000 : timestamp;
return new Date(ts);
}
/**
* Группирует массив сообщений по датам
* Возвращает структуру для рендеринга с dividers
*
* @param {Array} messages - Массив сообщений
* @returns {Array} Массив объектов { type: 'divider'|'message', data: ... }
*
* @example
* const grouped = DateDividerManager.groupMessages(messages);
* grouped.forEach(item => {
* if (item.type === 'divider') {
* container.appendChild(DateDividerManager.createDivider(item.text));
* } else {
* renderMessage(item.message);
* }
* });
*/
static groupMessages(messages) {
const result = [];
let lastDay = null;
for (const message of messages) {
const date = this.getMessageDate(message);
const day = this.formatDay(date);
if (this.shouldAddDivider(day, lastDay)) {
result.push({
type: 'divider',
text: day,
timestamp: date.getTime()
});
lastDay = day;
}
result.push({
type: 'message',
message: message,
day: day
});
}
return result;
}
/**
* Находит date divider по тексту даты в контейнере
*
* @param {HTMLElement} container - Контейнер для поиска
* @param {string} dateText - Текст даты
* @returns {HTMLElement|null} Найденный divider или null
*
* @example
* const divider = DateDividerManager.findDivider(chatScroll, 'Сегодня');
* if (divider) {
* divider.scrollIntoView();
* }
*/
static findDivider(container, dateText) {
return container.querySelector(`[data-date-divider="${dateText}"], [data-date-text="${dateText}"]`);
}
/**
* Обновляет текст существующего divider (для live update)
* Например, когда "Сегодня" становится "Вчера" после полуночи
*
* @param {HTMLElement} divider - DOM элемент divider
* @param {string} newText - Новый текст
*
* @example
* // После полуночи обновляем даты
* const dividers = document.querySelectorAll('[data-date-divider]');
* dividers.forEach(div => {
* const date = new Date(div.dataset.timestamp);
* const newText = DateDividerManager.formatDay(date);
* DateDividerManager.updateDivider(div, newText);
* });
*/
static updateDivider(divider, newText) {
const bubble = divider.querySelector('span');
if (bubble) {
bubble.textContent = escapeHtml(newText);
divider.setAttribute('data-date-divider', newText);
}
}
/**
* Удаляет все date dividers из контейнера
* Полезно для полной перерисовки
*
* @param {HTMLElement} container - Контейнер
*
* @example
* DateDividerManager.clearDividers(chatScroll);
* // Теперь можно заново отрендерить сообщения с dividers
*/
static clearDividers(container) {
const dividers = container.querySelectorAll('[data-date-divider], .sticky-date');
dividers.forEach(div => div.remove());
}
}
/**
* Экспортируем отдельные функции для удобства
*/
export const {
formatDay: formatDayExport,
createDivider,
createStickyDate,
shouldAddDivider,
getMessageDate,
groupMessages,
findDivider,
updateDivider,
clearDividers
} = DateDividerManager;
export default DateDividerManager;