- Исправлен PollSerializer для создания опросов с опциями - Добавлено поле read_only_fields для корректной валидации - Реализована отправка WebSocket уведомлений при создании опроса - Исправлен импорт serialize_message из communications.serialization - Добавлен обработчик poll_update в UserConsumer для WebSocket событий - Исправлен эндпоинт /results/ для возврата user_voted_option_ids - Добавлено поле voters в PollOptionSerializer для списка проголосовавших - Реализована инициализация poll виджетов после рендера сообщений - Добавлено игнорирование poll_update в notification-manager - Исправлены стили для request list badges (height, word-break, flex) - Восстановлены стили team-wheel (.wheel-focus, .scroll-arc) из git истории - Реализовано отображение forwarded message info в messageRendererV2 - Множественные улучшения UI и UX для опросов Fixes: создание опросов, голосование, отображение результатов, список проголосовавших
345 lines
15 KiB
HTML
345 lines
15 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Тест функции ответа на сообщение</title>
|
||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
|
||
<style>
|
||
body {
|
||
padding: 20px;
|
||
background: #f5f5f5;
|
||
}
|
||
.test-section {
|
||
background: white;
|
||
border-radius: 8px;
|
||
padding: 20px;
|
||
margin-bottom: 20px;
|
||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||
}
|
||
.message-preview {
|
||
background: #e3f2fd;
|
||
border-radius: 12px;
|
||
padding: 12px;
|
||
margin: 10px 0;
|
||
}
|
||
.bubble {
|
||
background: #007bff;
|
||
color: white;
|
||
border-radius: 12px;
|
||
padding: 12px 16px;
|
||
max-width: 400px;
|
||
}
|
||
.reply-indicator {
|
||
background: #fff3cd;
|
||
border-left: 4px solid #ffc107;
|
||
padding: 12px;
|
||
margin: 10px 0;
|
||
border-radius: 4px;
|
||
}
|
||
.log-output {
|
||
background: #f8f9fa;
|
||
border: 1px solid #dee2e6;
|
||
border-radius: 4px;
|
||
padding: 12px;
|
||
font-family: 'Courier New', monospace;
|
||
font-size: 12px;
|
||
max-height: 300px;
|
||
overflow-y: auto;
|
||
margin-top: 10px;
|
||
}
|
||
.log-entry {
|
||
margin: 4px 0;
|
||
padding: 4px;
|
||
}
|
||
.log-entry.info { color: #0d6efd; }
|
||
.log-entry.warn { color: #ffc107; }
|
||
.log-entry.error { color: #dc3545; }
|
||
.log-entry.success { color: #198754; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<h1 class="mb-4">🧪 Тест функции ответа на сообщение</h1>
|
||
|
||
<!-- Статус загрузки реакций -->
|
||
<div class="test-section">
|
||
<h3>📊 Статус загрузки реакций</h3>
|
||
<div id="reactionsStatus" class="alert alert-info">
|
||
Проверка...
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Тест 1: Контекстное меню -->
|
||
<div class="test-section">
|
||
<h3>1️⃣ Тест контекстного меню</h3>
|
||
<p>Симуляция вызова функции ответа из контекстного меню</p>
|
||
|
||
<div class="message-preview" data-message-id="123">
|
||
<div class="small text-secondary mb-2">
|
||
Иван Петров · 10:30
|
||
</div>
|
||
<div class="bubble">
|
||
Привет! Как дела с проектом?
|
||
</div>
|
||
</div>
|
||
|
||
<button id="testReplyBtn" class="btn btn-primary">
|
||
<i class="bi bi-reply-fill"></i> Ответить (через контекстное меню)
|
||
</button>
|
||
|
||
<div id="testReplyResult" class="mt-3"></div>
|
||
</div>
|
||
|
||
<!-- Тест 2: ChatFormManager -->
|
||
<div class="test-section">
|
||
<h3>2️⃣ Тест ChatFormManager</h3>
|
||
<p>Проверка состояния формы при ответе</p>
|
||
|
||
<form id="chatForm" action="/upload/" method="POST">
|
||
<input type="hidden" name="chat_id" value="1">
|
||
|
||
<div class="mb-3">
|
||
<textarea id="id_content" class="form-control" rows="3" placeholder="Напишите сообщение..."></textarea>
|
||
</div>
|
||
|
||
<div id="replyIndicatorContainer"></div>
|
||
|
||
<button type="submit" class="btn btn-success">
|
||
<i class="bi bi-send-fill"></i> Отправить
|
||
</button>
|
||
<button type="button" id="cancelBtn" class="btn btn-secondary">
|
||
Отмена
|
||
</button>
|
||
</form>
|
||
|
||
<div id="formState" class="mt-3 p-3 bg-light border rounded">
|
||
<strong>Состояние формы:</strong>
|
||
<pre id="formStateJson">{ "mode": "send" }</pre>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Тест 3: Превью ответа в сообщении -->
|
||
<div class="test-section">
|
||
<h3>3️⃣ Тест превью ответа в сообщении</h3>
|
||
<p>Проверка рендеринга превью при ответе на сообщение</p>
|
||
|
||
<div class="message-preview">
|
||
<div class="message-reply-preview border-start border-3 border-primary ps-2 mb-2 ms-2 rounded"
|
||
data-reply-to-id="100"
|
||
style="background-color: rgba(13, 110, 253, 0.05); padding: 6px 8px; cursor: pointer;">
|
||
<div class="small fw-semibold text-primary">
|
||
<i class="bi bi-reply-fill me-1"></i>
|
||
Мария Иванова
|
||
</div>
|
||
<div class="small text-muted text-truncate" style="max-width: 400px;">
|
||
Давайте встретимся завтра
|
||
</div>
|
||
</div>
|
||
|
||
<div class="bubble">
|
||
Отлично, в какое время удобно?
|
||
</div>
|
||
</div>
|
||
|
||
<button id="testPreviewClick" class="btn btn-primary">
|
||
<i class="bi bi-cursor-fill"></i> Клик по превью (переход к сообщению)
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Логи -->
|
||
<div class="test-section">
|
||
<h3>📝 Журнал событий</h3>
|
||
<button id="clearLogs" class="btn btn-sm btn-outline-secondary mb-2">Очистить</button>
|
||
<div id="logOutput" class="log-output"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Модули -->
|
||
<script type="module">
|
||
import { initChatFormManager } from './static/js/components/chatFormManager.js';
|
||
import MessageContextMenu from './static/js/components/messageContextMenu.js';
|
||
import reactionsConfig from './static/js/config/reactionsConfig.js';
|
||
|
||
// Логирование
|
||
const logOutput = document.getElementById('logOutput');
|
||
|
||
function addLog(message, type = 'info') {
|
||
const entry = document.createElement('div');
|
||
entry.className = `log-entry ${type}`;
|
||
entry.textContent = `[${new Date().toLocaleTimeString()}] ${message}`;
|
||
logOutput.appendChild(entry);
|
||
logOutput.scrollTop = logOutput.scrollHeight;
|
||
}
|
||
|
||
document.getElementById('clearLogs').addEventListener('click', () => {
|
||
logOutput.innerHTML = '';
|
||
addLog('Логи очищены', 'info');
|
||
});
|
||
|
||
// Проверка загрузки реакций
|
||
async function checkReactions() {
|
||
addLog('Проверка загрузки реакций...', 'info');
|
||
|
||
try {
|
||
await reactionsConfig.load();
|
||
const emojis = reactionsConfig.getEmojis();
|
||
|
||
const statusEl = document.getElementById('reactionsStatus');
|
||
|
||
if (emojis && emojis.length > 0) {
|
||
statusEl.className = 'alert alert-success';
|
||
statusEl.innerHTML = `
|
||
<h5>✅ Реакции загружены успешно</h5>
|
||
<p>Всего доступно: <strong>${emojis.length}</strong> реакций</p>
|
||
<p>Эмодзи: ${emojis.join(' ')}</p>
|
||
`;
|
||
addLog(`✓ Загружено ${emojis.length} реакций: ${emojis.join(' ')}`, 'success');
|
||
} else {
|
||
statusEl.className = 'alert alert-warning';
|
||
statusEl.innerHTML = `
|
||
<h5>⚠️ Используются дефолтные реакции</h5>
|
||
<p>API эндпоинт не доступен, используются реакции по умолчанию</p>
|
||
`;
|
||
addLog('⚠ Используются дефолтные реакции (API недоступен)', 'warn');
|
||
}
|
||
} catch (error) {
|
||
const statusEl = document.getElementById('reactionsStatus');
|
||
statusEl.className = 'alert alert-danger';
|
||
statusEl.innerHTML = `
|
||
<h5>❌ Ошибка загрузки реакций</h5>
|
||
<p>${error.message}</p>
|
||
`;
|
||
addLog(`✗ Ошибка: ${error.message}`, 'error');
|
||
}
|
||
}
|
||
|
||
// Инициализация ChatFormManager
|
||
addLog('Инициализация ChatFormManager...', 'info');
|
||
const chatFormManager = initChatFormManager({
|
||
formId: 'chatForm',
|
||
textareaId: 'id_content',
|
||
chatId: 1,
|
||
uploadUrl: '/api/v1/communications/messages/upload/',
|
||
editUrlTemplate: '/api/v1/communications/messages/{id}/'
|
||
});
|
||
|
||
if (chatFormManager) {
|
||
window.chatFormManager = chatFormManager;
|
||
addLog('✓ ChatFormManager инициализирован', 'success');
|
||
} else {
|
||
addLog('✗ Ошибка инициализации ChatFormManager', 'error');
|
||
}
|
||
|
||
// Обновление отображения состояния формы
|
||
function updateFormState() {
|
||
if (!chatFormManager) return;
|
||
|
||
const state = chatFormManager.getFormData();
|
||
document.getElementById('formStateJson').textContent = JSON.stringify(state, null, 2);
|
||
}
|
||
|
||
// Тест 1: Ответ через контекстное меню
|
||
document.getElementById('testReplyBtn').addEventListener('click', () => {
|
||
addLog('Тест: Вызов handleReply из контекстного меню', 'info');
|
||
|
||
const messageEl = document.querySelector('[data-message-id="123"]');
|
||
|
||
if (!chatFormManager) {
|
||
addLog('✗ ChatFormManager не доступен', 'error');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// Симуляция логики MessageContextMenu.handleReply
|
||
const bubble = messageEl.querySelector('.bubble');
|
||
const messageText = bubble.textContent.trim();
|
||
const authorName = 'Иван Петров';
|
||
const messageId = messageEl.dataset.messageId;
|
||
|
||
addLog(`handleReply вызван с: ID=${messageId}, Автор="${authorName}"`, 'info');
|
||
|
||
chatFormManager.setModeToReply(messageId, authorName, messageText);
|
||
addLog('✓ Режим ответа активирован', 'success');
|
||
|
||
updateFormState();
|
||
|
||
// Показываем результат
|
||
const resultDiv = document.getElementById('testReplyResult');
|
||
resultDiv.innerHTML = `
|
||
<div class="alert alert-success">
|
||
<h5>✅ Тест пройден!</h5>
|
||
<ul>
|
||
<li>Режим формы: <strong>reply</strong></li>
|
||
<li>ID сообщения: <strong>${messageId}</strong></li>
|
||
<li>Placeholder обновлен: "${document.getElementById('id_content').placeholder}"</li>
|
||
<li>Hidden input reply_to добавлен: ${document.querySelector('input[name="reply_to"]') ? 'Да' : 'Нет'}</li>
|
||
</ul>
|
||
</div>
|
||
`;
|
||
} catch (error) {
|
||
addLog(`✗ Ошибка в handleReply: ${error.message}`, 'error');
|
||
|
||
const resultDiv = document.getElementById('testReplyResult');
|
||
resultDiv.innerHTML = `
|
||
<div class="alert alert-danger">
|
||
<h5>❌ Тест провален</h5>
|
||
<p>${error.message}</p>
|
||
</div>
|
||
`;
|
||
}
|
||
});
|
||
|
||
// Отмена
|
||
document.getElementById('cancelBtn').addEventListener('click', (e) => {
|
||
e.preventDefault();
|
||
addLog('Отмена действия (возврат в режим send)', 'info');
|
||
|
||
if (chatFormManager) {
|
||
chatFormManager.cancel();
|
||
addLog('✓ Режим send восстановлен', 'success');
|
||
updateFormState();
|
||
}
|
||
});
|
||
|
||
// Тест 3: Клик по превью
|
||
document.getElementById('testPreviewClick').addEventListener('click', () => {
|
||
const preview = document.querySelector('.message-reply-preview');
|
||
const replyToId = preview.dataset.replyToId;
|
||
|
||
addLog(`Клик по превью ответа, ID оригинального сообщения: ${replyToId}`, 'info');
|
||
addLog('В реальной ситуации произойдет переход к сообщению #' + replyToId, 'info');
|
||
|
||
// В реальности здесь будет вызов messageLoader.loadAroundMessage()
|
||
alert(`Переход к сообщению #${replyToId}\n\nВ реальной реализации вызывается:\nmessageLoader.loadAroundMessage(${replyToId})`);
|
||
});
|
||
|
||
// Мониторинг изменений в форме
|
||
document.getElementById('chatForm').addEventListener('submit', (e) => {
|
||
e.preventDefault();
|
||
|
||
addLog('=== ОТПРАВКА ФОРМЫ ===', 'info');
|
||
const formData = new FormData(e.target);
|
||
|
||
for (let [key, value] of formData.entries()) {
|
||
addLog(` ${key}: ${value}`, 'info');
|
||
}
|
||
|
||
const state = chatFormManager.getFormData();
|
||
addLog(`Режим: ${state.mode}`, 'info');
|
||
if (state.replyToMessageId) {
|
||
addLog(`Reply To: ${state.replyToMessageId}`, 'success');
|
||
}
|
||
|
||
addLog('=== КОНЕЦ ===', 'info');
|
||
});
|
||
|
||
// Запуск проверки реакций
|
||
checkReactions();
|
||
addLog('Тестовая страница загружена', 'success');
|
||
updateFormState();
|
||
</script>
|
||
</body>
|
||
</html>
|