refactor: удалены test/example файлы и deprecated templates

- Deleted chatHistoryLoader.js (@deprecated)
- Deleted chatControllerUsage.js (example)
- Deleted chatTests.js (manual test, 1732 lines)
- Deleted test_message_editing.html
- Deleted test_message_renderer.html
- Deleted chat_detail_scripts.html (неиспользуемый template)
This commit is contained in:
Игорь Надеин 2026-01-13 10:18:12 +03:00
parent 697f8c6d37
commit 2a012c297f
6 changed files with 0 additions and 2869 deletions

View File

@ -1,274 +0,0 @@
/**
* @fileoverview Chat History Loader - DEPRECATED
* @module components/chatHistoryLoader
*
* @deprecated Используйте ChatControllerV2 вместо этого модуля.
* Этот модуль сохранен для обратной совместимости.
*
* МИГРАЦИЯ:
* - Вместо initChatHistoryLoader() используйте ChatControllerV2
* - Вместо loadMore() используйте chatController.loadMoreHistory()
* - Вся логика загрузки теперь в MessageLoaderV2 и ScrollManagerV2
*
* @see controllers/chatControllerV2.js
* @see loaders/messageLoaderV2.js
* @see managers/scrollManagerV2.js
*/
import {
createMessageElement,
formatDay,
toTimestamp
} from './chatMessageTemplates.js';
const DEFAULT_CONFIG = {
scrollSelector: '#chatScroll',
loaderSelector: '[data-history-loader]',
triggerThreshold: 140,
pageSize: 30
};
function parseBool(value) {
if (typeof value === 'boolean') return value;
return value === '1' || value === 'true';
}
function getAvatarMapSnapshot() {
if (window?.chatAvatarMap?.getAll) {
return window.chatAvatarMap.getAll();
}
if (window?.__CHAT_AVATARS__) {
return { ...window.__CHAT_AVATARS__ };
}
return {};
}
function updateOldestDataset(scrollEl) {
const firstMsg = scrollEl.querySelector('.msg[data-id]');
if (!firstMsg) {
scrollEl.dataset.oldestId = '';
scrollEl.dataset.oldestTs = '';
return null;
}
scrollEl.dataset.oldestId = String(firstMsg.dataset.id || '');
scrollEl.dataset.oldestTs = String(firstMsg.dataset.ts || '');
return firstMsg;
}
function ensureLoader(scrollEl, selector) {
let loader = scrollEl.querySelector(selector);
if (!loader) {
loader = document.createElement('div');
loader.className = 'history-loader d-none';
loader.setAttribute('data-history-loader', '1');
loader.innerHTML = `
<div class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></div>
<span class="small">Загружаем ранние сообщения…</span>
`;
scrollEl.prepend(loader);
}
return loader;
}
export function initChatHistoryLoader(options = {}) {
const config = { ...DEFAULT_CONFIG, ...options };
const scrollEl = document.querySelector(config.scrollSelector);
if (!scrollEl) return null;
const fetchUrl = scrollEl.dataset.fetchUrl;
if (!fetchUrl) return null;
const loaderEl = ensureLoader(scrollEl, config.loaderSelector);
const meId = Number(scrollEl.dataset.meId || 0);
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,
hasMore: parseBool(scrollEl.dataset.hasMore || false),
oldestId: (() => {
const raw = scrollEl.dataset.oldestId;
if (!raw || raw === 'None') return null;
return raw;
})(),
fetchUrl,
limit: Number(scrollEl.dataset.pageSize || config.pageSize) || config.pageSize
};
function showLoader() {
loaderEl.classList.remove('d-none');
}
function hideLoader() {
loaderEl.classList.add('d-none');
}
function dedupeDayDividers(scrollEl) {
const dividerEls = scrollEl.querySelectorAll('.day-divider');
let lastLabel = null;
dividerEls.forEach((divider) => {
const label = divider.textContent.trim();
if (label === lastLabel) {
divider.remove();
} else {
lastLabel = label;
}
});
}
function prependMessages(messages = [], ctx) {
if (!messages.length) {
return;
}
const {
scrollEl,
loaderEl,
meId,
profileUrl,
detailUrlTemplate,
avatarMap,
messageRenderer,
state
} = ctx;
const prevHeight = scrollEl.scrollHeight;
const fragment = document.createDocumentFragment();
let prevDay = null;
// РЕФАКТОРИНГ: Создаем day-dividers вручную с правильным стилем
// Используем messageRenderer.formatDay() для единообразия
messages.forEach((msg) => {
// Определяем день сообщения
const ts = toTimestamp(msg);
const msgDate = new Date(ts);
const day = messageRenderer ? messageRenderer.formatDay(msgDate) : formatDay(ts);
// Создаем day-divider если день изменился
if (day !== prevDay) {
// Создаем divider с ПРАВИЛЬНЫМ стилем (как в messageRenderer)
const dividerEl = document.createElement('div');
dividerEl.className = 'day-divider text-center small text-muted my-3';
dividerEl.innerHTML = `<span class="px-3 py-1 rounded-pill bg-light">${day}</span>`;
fragment.appendChild(dividerEl);
}
prevDay = day;
// Создаем элемент сообщения
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);
});
const anchor = loaderEl.nextSibling || scrollEl.firstChild;
scrollEl.insertBefore(fragment, anchor);
const delta = scrollEl.scrollHeight - prevHeight;
scrollEl.scrollTop += delta;
dedupeDayDividers(scrollEl);
const firstMsg = updateOldestDataset(scrollEl);
if (firstMsg) {
state.oldestId = firstMsg.dataset.id;
}
}
async function loadMore() {
if (state.loading || !state.hasMore || !state.oldestId) {
return;
}
state.loading = true;
showLoader();
try {
const url = new URL(state.fetchUrl, window.location.origin);
url.searchParams.set('limit', String(state.limit));
url.searchParams.set('before_id', state.oldestId);
const response = await fetch(url.toString(), {
headers: {
'X-Requested-With': 'XMLHttpRequest'
},
credentials: 'same-origin'
});
const payload = await response.json();
if (!response.ok || !payload.ok) {
throw new Error(payload.error || 'Не удалось загрузить сообщения');
}
if (Array.isArray(payload.messages) && payload.messages.length) {
prependMessages(payload.messages, {
scrollEl,
loaderEl,
meId,
profileUrl,
detailUrlTemplate,
avatarMap,
messageRenderer,
state
});
state.oldestId = String(payload.messages[0].id);
} else if (payload.next_before_id) {
state.oldestId = String(payload.next_before_id);
} else {
state.oldestId = null;
}
state.hasMore = Boolean(payload.has_more);
scrollEl.dataset.hasMore = state.hasMore ? '1' : '0';
} catch (error) {
console.error('ChatHistoryLoader: history request failed', error);
} finally {
state.loading = false;
hideLoader();
}
}
function handleScroll() {
if (!state.hasMore || state.loading) {
return;
}
if (scrollEl.scrollTop <= config.triggerThreshold) {
loadMore();
}
}
scrollEl.addEventListener('scroll', handleScroll, { passive: true });
if (!state.hasMore) {
hideLoader();
} else if (scrollEl.scrollHeight <= scrollEl.clientHeight + config.triggerThreshold) {
// Если сообщений мало и страница «короткая», сразу догружаем ещё
loadMore();
}
return {
loadMore,
hasMore: () => state.hasMore
};
}
// УДАЛЕНО: Авто-инициализация теперь происходит через chat_detail_scripts.html
// if (document.readyState === 'loading') {
// document.addEventListener('DOMContentLoaded', () => initChatHistoryLoader());
// } else {
// initChatHistoryLoader();
// }

View File

@ -1,220 +0,0 @@
/**
* @fileoverview Пример использования новой архитектуры
*
* Демонстрирует как использовать ChatController
* вместо старых разрозненных модулей
*/
import { ChatController } from './controllers/chatController.js';
// ==================== СТАРЫЙ СПОСОБ (deprecated) ====================
/*
// Было раньше - много разных модулей:
import { initUserWebSocket } from './components/userWebSocket.js';
import { MessageRenderer } from './components/messageRenderer.js';
import { initChatHistoryLoader } from './components/chatHistoryLoader.js';
import { initChatMarkRead } from './components/chatMarkRead.js';
const scrollEl = document.getElementById('chatScroll');
const chatId = parseInt(scrollEl.dataset.chatId);
// Создаем renderer
const messageRenderer = new MessageRenderer({
containerId: 'chatScroll',
currentUserId: userId,
profileUrl: profileUrl,
detailUrlTemplate: detailUrlTemplate
});
// Инициализируем WebSocket
const userWs = initUserWebSocket({ userId });
userWs.configure({
chatId,
messageRenderer,
scrollContainerId: 'chatScroll'
});
// Инициализируем History Loader
initChatHistoryLoader({
messageRenderer,
scrollContainerId: 'chatScroll',
fetchUrl: `/api/v1/communications/chats/${chatId}/messages/`
});
// И еще кучу других модулей...
*/
// ==================== НОВЫЙ СПОСОБ (recommended) ====================
/**
* Инициализация чата с новой архитектурой
*/
async function initChat() {
// Получаем данные из DOM
const scrollEl = document.getElementById('chatScroll');
const chatId = parseInt(scrollEl.dataset.chatId);
const currentUserId = parseInt(document.body.dataset.userId);
// Получаем WebSocket (он уже инициализирован в base.html)
const ws = window.userWebSocket;
// Создаем ChatController - ВСЁ в одном!
const chatController = new ChatController({
chatId,
currentUserId,
scrollElement: scrollEl,
containerId: 'chatScroll',
wsConnection: ws,
profileUrl: '/employees/profile/',
detailUrlTemplate: '/employees/detail/0/'
});
// Инициализируем (загрузка + рендеринг + scroll)
await chatController.init();
// Готово! ChatController управляет всем:
// - MessageStore (хранилище)
// - MessageLoader (загрузка)
// - MessageRenderer (рендеринг)
// - ScrollManager (прокрутка)
// Экспортируем в window для доступа из других модулей
window.chatController = chatController;
console.log('✅ Chat initialized with new architecture!');
console.log('Status:', chatController.getStatus());
}
// ==================== ИСПОЛЬЗОВАНИЕ ====================
/**
* Отправка сообщения
*/
function sendMessage() {
const textarea = document.getElementById('messageInput');
const content = textarea.value.trim();
if (!content) return;
// Просто вызываем метод контроллера
const tempId = window.chatController.sendMessage(content);
console.log('Message sent with temp ID:', tempId);
// Очищаем textarea
textarea.value = '';
}
/**
* Прокрутка к последнему сообщению
*/
function scrollToLatest() {
window.chatController.scrollToBottom({ instant: false });
}
/**
* Загрузка истории
*/
async function loadMore() {
const messages = await window.chatController.loadMoreHistory();
console.log('Loaded', messages.length, 'historical messages');
}
/**
* Получение статуса чата
*/
function getStatus() {
const status = window.chatController.getStatus();
console.log('Chat status:', status);
return status;
}
/**
* Поиск сообщения
*/
function findMessage(messageId) {
const message = window.chatController.getMessage(messageId);
console.log('Message:', message);
return message;
}
/**
* Прокрутка к конкретному сообщению
*/
function goToMessage(messageId) {
window.chatController.scrollToMessage(messageId, {
block: 'center',
behavior: 'smooth'
});
}
// ==================== ИНТЕГРАЦИЯ С ДРУГИМИ МОДУЛЯМИ ====================
/**
* Интеграция с ChatComposer
*/
function integrateChatComposer() {
// ChatComposer теперь может использовать ChatController
const composer = {
sendMessage: (content, options) => {
return window.chatController.sendMessage(content, options);
},
isNearBottom: () => {
return window.chatController.isNearBottom();
}
};
return composer;
}
/**
* Интеграция с реакциями (reactions)
*/
window.addEventListener('chat:message-added', (event) => {
const { messageId, chatId } = event.detail;
// Инициализируем реакции для нового сообщения
console.log('New message added, initializing reactions:', messageId);
// MessageContextMenu автоматически подхватит через MutationObserver
});
/**
* Интеграция с уведомлениями о прочтении
*/
window.addEventListener('chat:message-added', (event) => {
const { messageId } = event.detail;
// Проверяем видимо ли сообщение
const isVisible = window.chatController.scrollManager.isNearBottom();
if (isVisible) {
// Отмечаем как прочитанное
// markAsRead(messageId);
}
});
// ==================== ЭКСПОРТ ====================
export {
initChat,
sendMessage,
scrollToLatest,
loadMore,
getStatus,
findMessage,
goToMessage,
integrateChatComposer
};
// Автозапуск если это страница чата
if (document.getElementById('chatScroll')) {
// Ждем когда DOM полностью загрузится
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initChat);
} else {
initChat();
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,85 +0,0 @@
{# includes/chat_detail_scripts.html #}
{# Скрипты только для страницы chat_detail.html #}
{% load static %}
<script type="module">
import { initChatMarkRead } from "{% static 'js/components/chatMarkRead.js' %}";
import { initChatComposer } from "{% static 'js/components/chatComposer.js' %}";
import { initChatHistoryLoader } from "{% static 'js/components/chatHistoryLoader.js' %}";
import { initChatFormManager } from "{% static 'js/components/chatFormManager.js' %}";
// Ждём готовности chat-системы
window.addEventListener('chat:system-ready', (e) => {
console.log('[ChatDetailScripts] chat:system-ready event received!', e.detail);
const { userWs, avatarMap, badgeManager } = e.detail;
// ========== FORM MANAGER ==========
// Управление режимами формы (send/edit/reply)
console.log('[ChatDetailScripts] Initializing FormManager...');
const formManager = initChatFormManager({
formId: 'chatForm',
textareaId: 'id_content',
chatId: {{ chat.id }},
uploadUrl: "{% url 'api:v1:upload_message' %}",
editUrlTemplate: "/api/v1/communications/messages/{id}/edit/"
});
// ========== MARK READ ==========
const markReadApi = initChatMarkRead({
chatId: {{ chat.id }},
meId: {{ user.id|default:"0" }},
scrollContainerId: 'chatScroll',
textareaId: 'id_content',
formId: 'chatForm',
scrollBtnId: 'scrollBtn',
markReadUrl: "{% url 'communications:chat_mark_read' chat.pk %}",
initialLastReadTs: {{ chat.last_read_timestamp|default:"null" }}
});
// ========== COMPOSER ==========
console.log('[ChatDetailScripts] Initializing Composer...');
const composer = initChatComposer({
chatId: {{ chat.id }},
formId: 'chatForm',
textareaId: 'id_content',
uploadUrl: "{% url 'api:v1:upload_message' %}",
userWs: userWs
});
console.log('[ChatDetailScripts] Composer initialized:', composer);
// ========== HISTORY LOADER ==========
const historyLoader = initChatHistoryLoader({
chatId: {{ chat.id }},
scrollContainerId: 'chatScroll',
fetchUrl: "{% url 'api:v1:chat_messages' chat.pk %}",
avatarMap: avatarMap.getAll(),
meId: {{ user.id }}
});
// Расширяем userWebSocket chat-специфичной конфигурацией
if (userWs.configure) {
userWs.configure({
scrollContainerId: 'chatScroll',
formId: 'chatForm',
textareaId: 'id_content',
typingIndicatorId: 'typing',
avatarMap: avatarMap.getAll(),
markReadApi: markReadApi
});
}
// Открываем чат в WebSocket
if (userWs.openChat) {
userWs.openChat({{ chat.id }}, false);
}
// Экспортируем API
window.chatComposer = composer;
window.chatHistoryLoader = historyLoader;
window.markReadApi = markReadApi;
window.chatFormManager = formManager;
});
</script>
{# UI Components #}
<script type="module" src="{% static 'js/chat-detail-enhanced.js' %}"></script>

View File

@ -1,369 +0,0 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Test Message Editing - Full Rerender</title>
<style>
body {
font-family: system-ui, -apple-system, sans-serif;
max-width: 800px;
margin: 20px auto;
padding: 20px;
}
.test-section {
border: 1px solid #ddd;
padding: 15px;
margin: 15px 0;
border-radius: 8px;
}
.test-section h3 {
margin-top: 0;
}
button {
padding: 8px 16px;
margin: 5px;
cursor: pointer;
}
#chatScroll {
border: 1px solid #ccc;
padding: 10px;
min-height: 200px;
background: #f9f9f9;
}
.msg {
display: flex;
margin-bottom: 15px;
}
.bubble {
padding: 10px;
border-radius: 8px;
background: white;
border: 1px solid #ddd;
}
.bubble-me {
background: #e3f2fd;
}
.message-reactions-wrapper {
margin-top: 5px;
font-size: 14px;
}
.reply-reference {
border-left: 3px solid #2196F3;
padding-left: 8px;
margin-bottom: 8px;
opacity: 0.8;
font-size: 13px;
}
.attachment {
margin-top: 8px;
padding: 8px;
background: #f5f5f5;
border-radius: 4px;
}
.message-edited-indicator {
color: #999;
font-size: 12px;
font-style: italic;
}
.log {
background: #000;
color: #0f0;
padding: 10px;
font-family: monospace;
font-size: 12px;
max-height: 300px;
overflow-y: auto;
margin-top: 10px;
}
</style>
</head>
<body>
<h1>🧪 Test: Message Editing with Full Rerender</h1>
<div class="test-section">
<h3>Текущее состояние</h3>
<div id="chatScroll" data-chat-id="1" data-me-id="123"></div>
</div>
<div class="test-section">
<h3>Тесты</h3>
<button onclick="testSimpleEdit()">1. Простое редактирование текста</button>
<button onclick="testWithAttachments()">2. Редактирование с вложениями</button>
<button onclick="testWithReply()">3. Редактирование с ответом</button>
<button onclick="testRemoveReply()">4. Удаление ответа</button>
<button onclick="testWithReactions()">5. Редактирование с реакциями</button>
<button onclick="clearChat()">🗑️ Очистить</button>
</div>
<div class="test-section">
<h3>Console Log</h3>
<div id="log" class="log"></div>
</div>
<script type="module">
import { MessageRenderer } from '/static/js/components/messageRenderer.js';
window.currentUserAvatar = '';
window.meId = 123;
const log = (msg, ...args) => {
console.log(msg, ...args);
const logEl = document.getElementById('log');
logEl.innerHTML += `<div>${new Date().toISOString().substr(11, 8)} - ${msg}</div>`;
logEl.scrollTop = logEl.scrollHeight;
};
window.testSimpleEdit = function() {
log('TEST 1: Simple text edit');
const renderer = new MessageRenderer({
containerId: 'chatScroll',
currentUserId: 123
});
// Исходное сообщение
const originalMsg = {
id: 1,
author_id: 123,
author_name: 'Вы',
content: 'Привет! Это исходный текст.',
created_ts: Date.now(),
is_edited: false,
reactions_summary: {}
};
renderer.renderMessage(originalMsg);
log('✓ Original message rendered');
setTimeout(() => {
// Редактированное сообщение
const editedMsg = {
...originalMsg,
content: 'Привет! Это ОТРЕДАКТИРОВАННЫЙ текст.',
is_edited: true,
edited_at: new Date().toISOString()
};
log('Simulating WebSocket edit event...');
window.dispatchEvent(new CustomEvent('chat:message-edited', {
detail: { payload: editedMsg }
}));
setTimeout(() => {
const el = document.querySelector('[data-message-id="1"]');
const hasIndicator = el?.querySelector('.message-edited-indicator');
log(hasIndicator ? '✓ Edit indicator found!' : '✗ Edit indicator NOT found');
}, 100);
}, 500);
};
window.testWithAttachments = function() {
log('TEST 2: Edit with attachments');
const renderer = new MessageRenderer({
containerId: 'chatScroll',
currentUserId: 456
});
const msgWithAttachments = {
id: 2,
author_id: 456,
author_name: 'User',
content: 'Смотрите фото!',
created_ts: Date.now(),
is_edited: false,
attachments: [
{ id: 1, file_name: 'photo1.jpg', file_type: 'image', file_url: '#' },
{ id: 2, file_name: 'photo2.jpg', file_type: 'image', file_url: '#' }
],
reactions_summary: {}
};
renderer.renderMessage(msgWithAttachments);
log('✓ Message with 2 attachments rendered');
setTimeout(() => {
const editedMsg = {
...msgWithAttachments,
content: 'Смотрите ОБНОВЛЕННОЕ фото!',
attachments: [
{ id: 1, file_name: 'photo1.jpg', file_type: 'image', file_url: '#' }
],
is_edited: true,
edited_at: new Date().toISOString()
};
log('Editing message (removed 1 attachment)...');
window.dispatchEvent(new CustomEvent('chat:message-edited', {
detail: { payload: editedMsg }
}));
setTimeout(() => {
const el = document.querySelector('[data-message-id="2"]');
const attachments = el?.querySelectorAll('.attachment, img');
log(`Attachments in DOM: ${attachments?.length || 0} (should be 1)`);
}, 100);
}, 500);
};
window.testWithReply = function() {
log('TEST 3: Edit message with reply');
const renderer = new MessageRenderer({
containerId: 'chatScroll',
currentUserId: 123
});
const msgWithReply = {
id: 3,
author_id: 123,
author_name: 'Вы',
content: 'Согласен!',
created_ts: Date.now(),
is_edited: false,
reply_to: {
id: 999,
author_name: 'Иван',
content: 'Идем завтра на встречу?'
},
reactions_summary: {}
};
renderer.renderMessage(msgWithReply);
log('✓ Message with reply rendered');
setTimeout(() => {
const editedMsg = {
...msgWithReply,
content: 'Полностью согласен!',
is_edited: true,
edited_at: new Date().toISOString()
};
log('Editing message (reply preserved)...');
window.dispatchEvent(new CustomEvent('chat:message-edited', {
detail: { payload: editedMsg }
}));
setTimeout(() => {
const el = document.querySelector('[data-message-id="3"]');
const replyBlock = el?.querySelector('.reply-reference');
log(replyBlock ? '✓ Reply block still present' : '✗ Reply block missing');
}, 100);
}, 500);
};
window.testRemoveReply = function() {
log('TEST 4: Remove reply on edit');
const renderer = new MessageRenderer({
containerId: 'chatScroll',
currentUserId: 123
});
const msgWithReply = {
id: 4,
author_id: 123,
author_name: 'Вы',
content: 'Отвечаю на вопрос',
created_ts: Date.now(),
is_edited: false,
reply_to: {
id: 998,
author_name: 'Петр',
content: 'Есть вопрос...'
},
reactions_summary: {}
};
renderer.renderMessage(msgWithReply);
log('✓ Message with reply rendered');
setTimeout(() => {
const editedMsg = {
...msgWithReply,
content: 'Просто сообщение без ответа',
reply_to: null, // ← Удалили ответ!
is_edited: true,
edited_at: new Date().toISOString()
};
log('Editing message (REMOVING reply)...');
window.dispatchEvent(new CustomEvent('chat:message-edited', {
detail: { payload: editedMsg }
}));
setTimeout(() => {
const el = document.querySelector('[data-message-id="4"]');
const replyBlock = el?.querySelector('.reply-reference');
log(replyBlock ? '✗ Reply block still present (BUG!)' : '✓ Reply block removed');
}, 100);
}, 500);
};
window.testWithReactions = function() {
log('TEST 5: Edit with reactions');
const renderer = new MessageRenderer({
containerId: 'chatScroll',
currentUserId: 123
});
const msgWithReactions = {
id: 5,
author_id: 456,
author_name: 'User',
content: 'Отличная идея!',
created_ts: Date.now(),
is_edited: false,
reactions_summary: {
'👍': { count: 3, users: [1, 2, 3] },
'❤️': { count: 1, users: [4] }
}
};
renderer.renderMessage(msgWithReactions);
log('✓ Message with reactions rendered');
setTimeout(() => {
const editedMsg = {
...msgWithReactions,
content: 'ОЧЕНЬ отличная идея!',
is_edited: true,
edited_at: new Date().toISOString(),
reactions_summary: {
'👍': { count: 5, users: [1, 2, 3, 5, 6] },
'❤️': { count: 2, users: [4, 7] },
'🔥': { count: 1, users: [8] }
}
};
log('Editing message (reactions updated)...');
window.dispatchEvent(new CustomEvent('chat:message-edited', {
detail: { payload: editedMsg }
}));
setTimeout(() => {
const el = document.querySelector('[data-message-id="5"]');
const reactionsData = JSON.parse(el?.dataset.reactions || '{}');
const reactionCount = Object.keys(reactionsData).length;
log(`Reactions in DOM: ${reactionCount} types (should be 3)`);
}, 100);
}, 500);
};
window.clearChat = function() {
document.getElementById('chatScroll').innerHTML = '';
document.getElementById('log').innerHTML = '';
log('✓ Cleared');
};
// Импортируем и инициализируем messageEditing
import { initMessageEditing } from '/static/js/components/messageEditing.js';
initMessageEditing();
log('✓ Message editing initialized');
log('Ready to test! Click buttons above.');
</script>
</body>
</html>

View File

@ -1,190 +0,0 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Test MessageRenderer</title>
<style>
body { font-family: system-ui; padding: 20px; max-width: 900px; margin: 0 auto; }
.test { border: 1px solid #ddd; padding: 15px; margin: 15px 0; }
pre { background: #f5f5f5; padding: 10px; overflow-x: auto; font-size: 12px; }
.result { border: 2px solid #4CAF50; padding: 10px; margin: 10px 0; }
button { padding: 8px 16px; margin: 5px; }
</style>
</head>
<body>
<h1>🧪 Test MessageRenderer.buildMessageHtml()</h1>
<div class="test">
<h3>Test 1: Message with Attachments</h3>
<button onclick="testAttachments()">Run Test</button>
<div id="result1"></div>
</div>
<div class="test">
<h3>Test 2: Message with Reply</h3>
<button onclick="testReply()">Run Test</button>
<div id="result2"></div>
</div>
<div class="test">
<h3>Test 3: Message with Forwarded Info</h3>
<button onclick="testForwarded()">Run Test</button>
<div id="result3"></div>
</div>
<script type="module">
import { MessageRenderer } from '/static/js/components/messageRenderer.js';
window.testAttachments = function() {
console.log('TEST 1: Message with attachments');
const renderer = new MessageRenderer({
currentUserId: 123
});
const message = {
id: 1,
author_id: 123,
author_name: 'Test User',
content: 'Message with files',
created_ts: Date.now(),
has_attachments: true,
attachments: [
{
id: 1,
file_name: 'document.pdf',
file_type: 'file',
file_url: '/media/files/document.pdf',
file_size: 1024000
},
{
id: 2,
file_name: 'photo.jpg',
file_type: 'image',
file_url: '/media/images/photo.jpg'
}
],
reactions_summary: {}
};
const html = renderer.buildMessageHtml(message, true);
console.log('Generated HTML length:', html.length);
console.log('Contains "attachment":', html.includes('attachment'));
console.log('Contains "document.pdf":', html.includes('document.pdf'));
console.log('Contains "photo.jpg":', html.includes('photo.jpg'));
document.getElementById('result1').innerHTML = `
<div class="result">
<strong>Generated HTML:</strong>
<pre>${escapeHtml(html)}</pre>
<strong>Rendered:</strong>
${html}
<strong>Checks:</strong>
<ul>
<li>HTML length: ${html.length} chars</li>
<li>Contains "attachment": ${html.includes('attachment') ? '✅' : '❌'}</li>
<li>Contains "document.pdf": ${html.includes('document.pdf') ? '✅' : '❌'}</li>
<li>Contains "photo.jpg": ${html.includes('photo.jpg') ? '✅' : '❌'}</li>
</ul>
</div>
`;
};
window.testReply = function() {
console.log('TEST 2: Message with reply');
const renderer = new MessageRenderer({
currentUserId: 456
});
const message = {
id: 2,
author_id: 456,
author_name: 'User 2',
content: 'This is a reply',
created_ts: Date.now(),
reply_to: {
id: 999,
author_name: 'Original Author',
content: 'Original message text here'
},
reactions_summary: {}
};
const html = renderer.buildMessageHtml(message, true);
console.log('Generated HTML length:', html.length);
console.log('Contains "reply-reference":', html.includes('reply-reference'));
console.log('Contains "Original Author":', html.includes('Original Author'));
document.getElementById('result2').innerHTML = `
<div class="result">
<strong>Generated HTML:</strong>
<pre>${escapeHtml(html)}</pre>
<strong>Rendered:</strong>
${html}
<strong>Checks:</strong>
<ul>
<li>Contains "reply-reference": ${html.includes('reply-reference') ? '✅' : '❌'}</li>
<li>Contains "Original Author": ${html.includes('Original Author') ? '✅' : '❌'}</li>
<li>Contains "Original message text": ${html.includes('Original message text') ? '✅' : '❌'}</li>
</ul>
</div>
`;
};
window.testForwarded = function() {
console.log('TEST 3: Message with forwarded info');
const renderer = new MessageRenderer({
currentUserId: 789
});
const message = {
id: 3,
author_id: 789,
author_name: 'User 3',
content: 'Forwarded content',
created_ts: Date.now(),
is_forwarded: true,
forwarded_from: {
author_name: 'Original Sender',
chat_name: 'Some Chat',
created_at: '01.12.2025 10:30'
},
reactions_summary: {}
};
const html = renderer.buildMessageHtml(message, false);
console.log('Generated HTML length:', html.length);
console.log('Contains "forwarded-indicator":', html.includes('forwarded-indicator'));
console.log('Contains "Переслано":', html.includes('Переслано'));
document.getElementById('result3').innerHTML = `
<div class="result">
<strong>Generated HTML:</strong>
<pre>${escapeHtml(html.substring(0, 1000))}...</pre>
<strong>Rendered:</strong>
${html}
<strong>Checks:</strong>
<ul>
<li>Contains "forwarded-indicator": ${html.includes('forwarded-indicator') ? '✅' : '❌'}</li>
<li>Contains "Переслано": ${html.includes('Переслано') ? '✅' : '❌'}</li>
<li>Contains "Original Sender": ${html.includes('Original Sender') ? '✅' : '❌'}</li>
</ul>
</div>
`;
};
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
console.log('✓ Test page ready. Click buttons to run tests.');
</script>
</body>
</html>