Redesign: современный интерфейс чата в стиле мессенджеров

Изменения в chat_detail.html:
- Убрана кнопка с фотографией из композера
- Кнопка вложений встроена в строку ввода с выпадающим меню
- Добавлено выпадающее меню с опциями: Документ, Фото/видео, Камера, Аудио
- Добавлены скрытые input для каждого типа файлов
- Реализовано превью прикрепленных файлов
- Добавлен JavaScript для обработки выбора и отображения файлов

Изменения в chat-detail.css:
- Обновлен стиль композера под современные мессенджеры
- Круглые кнопки вложений и отправки (40px)
- Поле ввода с закругленными углами (border-radius: 20px)
- Анимации hover для кнопок (scale 1.05)
- Стили для превью вложений
- Улучшенное выпадающее меню с иконками и transition эффектами
This commit is contained in:
admin i 2025-11-27 17:55:02 +03:00
parent 55a450e30c
commit 64e23e32ec
2 changed files with 264 additions and 7 deletions

View File

@ -115,7 +115,7 @@ main.main {
}
/* ─────────────────────────────────────────────────────────────
COMPOSER
COMPOSER (Modern Messenger Style)
────────────────────────────────────────────────────────────── */
.chat-composer {
position: sticky;
@ -133,11 +133,120 @@ main.main {
.composer-wrap {
display: flex;
align-items: end;
flex-direction: column;
gap: var(--space-1);
}
.composer-input-group {
display: flex;
align-items: flex-end;
gap: var(--space-1);
flex-wrap: nowrap;
}
.composer-textarea-wrapper {
flex: 1 1 auto;
position: relative;
min-width: 0;
}
.composer-textarea {
resize: none;
max-height: 120px;
border-radius: 20px;
padding: 10px 16px;
line-height: 1.4;
border: 1px solid var(--bs-border-color);
background: var(--bs-body-bg);
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
}
.composer-textarea:focus {
border-color: var(--bs-primary);
box-shadow: 0 0 0 0.2rem rgba(var(--bs-primary-rgb), 0.15);
}
.btn-attachment {
padding: 8px 12px;
border-radius: 50%;
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
}
.btn-attachment:hover {
background: var(--bs-secondary-bg);
transform: scale(1.05);
}
.btn-send {
padding: 10px 16px;
border-radius: 50%;
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
}
.btn-send:hover {
transform: scale(1.05);
}
.btn-send i {
font-size: 1.1rem;
}
/* Превью вложений */
.attachment-preview {
padding: var(--space-1) 0;
max-height: 200px;
overflow-y: auto;
}
.attachment-item {
background: var(--bs-secondary-bg);
transition: background-color 0.15s ease;
}
.attachment-item:hover {
background: var(--bs-tertiary-bg);
}
.attachment-item .btn-ghost {
opacity: 0.6;
}
.attachment-item .btn-ghost:hover {
opacity: 1;
}
/* Выпадающее меню вложений */
.dropdown-menu {
min-width: 200px;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
padding: 8px;
}
.dropdown-item {
border-radius: 8px;
padding: 10px 12px;
transition: background-color 0.15s ease;
}
.dropdown-item:hover {
background: var(--bs-secondary-bg);
}
.dropdown-item i {
width: 24px;
}
.composer-tools {
display: flex;
gap: var(--space-1);

View File

@ -110,12 +110,68 @@
<div id="typing" class="small text-secondary ps-1 pt-1 d-none">Собеседник печатает…</div>
<form method="post" id="chatForm" class="composer-wrap">
{% csrf_token %}
<div class="composer-tools">
<button type="button" class="btn btn-ghost" title="Вложить файл"><i class="bi-paperclip"></i></button>
<button type="button" class="btn btn-ghost" title="Камера"><i class="bi-camera"></i></button>
<div class="composer-input-group">
<!-- Кнопка вложений с выпадающим меню -->
<div class="dropdown">
<button type="button" class="btn btn-ghost btn-attachment"
id="attachmentDropdown"
data-bs-toggle="dropdown"
aria-expanded="false"
title="Прикрепить файл">
<i class="bi-paperclip"></i>
</button>
<ul class="dropdown-menu dropdown-menu-start" aria-labelledby="attachmentDropdown">
<li>
<a class="dropdown-item" href="#" id="attachDocument">
<i class="bi-file-earmark text-primary me-2"></i>
Документ
</a>
</li>
<li>
<a class="dropdown-item" href="#" id="attachImage">
<i class="bi-image text-success me-2"></i>
Фото или видео
</a>
</li>
<li>
<a class="dropdown-item" href="#" id="attachCamera">
<i class="bi-camera text-info me-2"></i>
Камера
</a>
</li>
<li>
<a class="dropdown-item" href="#" id="attachAudio">
<i class="bi-mic text-danger me-2"></i>
Аудио
</a>
</li>
</ul>
</div>
<!-- Скрытые input для файлов -->
<input type="file" id="documentInput" class="d-none" accept=".pdf,.doc,.docx,.xls,.xlsx,.txt,.zip,.rar">
<input type="file" id="imageInput" class="d-none" accept="image/*,video/*" multiple>
<input type="file" id="cameraInput" class="d-none" accept="image/*" capture="environment">
<input type="file" id="audioInput" class="d-none" accept="audio/*">
<!-- Поле ввода текста -->
<div class="composer-textarea-wrapper">
<textarea name="content"
id="id_content"
class="form-control composer-textarea"
rows="1"
placeholder="Напишите сообщение…"
required></textarea>
</div>
<!-- Кнопка отправки -->
<button type="submit" class="btn btn-primary btn-send" title="Отправить">
<i class="bi-send-fill"></i>
</button>
</div>
<textarea name="content" id="id_content" class="form-control flex-grow-1" rows="1" placeholder="Напишите сообщение…" required></textarea>
<button type="submit" class="btn btn-primary"><i class="bi-send"></i></button>
<!-- Превью прикрепленных файлов -->
<div id="attachmentPreview" class="attachment-preview d-none"></div>
</form>
</div>
</article>
@ -124,5 +180,97 @@
</div>
</div>
<script>
// Обработка вложений
document.addEventListener('DOMContentLoaded', function() {
const attachDocument = document.getElementById('attachDocument');
const attachImage = document.getElementById('attachImage');
const attachCamera = document.getElementById('attachCamera');
const attachAudio = document.getElementById('attachAudio');
const documentInput = document.getElementById('documentInput');
const imageInput = document.getElementById('imageInput');
const cameraInput = document.getElementById('cameraInput');
const audioInput = document.getElementById('audioInput');
const attachmentPreview = document.getElementById('attachmentPreview');
// Обработчики кликов на пункты меню
attachDocument?.addEventListener('click', (e) => {
e.preventDefault();
documentInput.click();
});
attachImage?.addEventListener('click', (e) => {
e.preventDefault();
imageInput.click();
});
attachCamera?.addEventListener('click', (e) => {
e.preventDefault();
cameraInput.click();
});
attachAudio?.addEventListener('click', (e) => {
e.preventDefault();
audioInput.click();
});
// Обработка выбора файлов
function handleFileSelect(files) {
if (!files || files.length === 0) return;
attachmentPreview.classList.remove('d-none');
attachmentPreview.innerHTML = '';
Array.from(files).forEach((file, index) => {
const fileItem = document.createElement('div');
fileItem.className = 'attachment-item d-flex align-items-center gap-2 p-2 border rounded mb-2';
// Иконка в зависимости от типа файла
let icon = 'bi-file-earmark';
if (file.type.startsWith('image/')) icon = 'bi-image';
else if (file.type.startsWith('video/')) icon = 'bi-camera-video';
else if (file.type.startsWith('audio/')) icon = 'bi-music-note';
else if (file.type.includes('pdf')) icon = 'bi-file-pdf';
fileItem.innerHTML = `
<i class="${icon} fs-4 text-primary"></i>
<div class="flex-grow-1">
<div class="fw-semibold">${file.name}</div>
<div class="small text-secondary">${formatFileSize(file.size)}</div>
</div>
<button type="button" class="btn btn-sm btn-ghost" onclick="removeAttachment(${index})">
<i class="bi-x-lg"></i>
</button>
`;
attachmentPreview.appendChild(fileItem);
});
}
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';
}
documentInput?.addEventListener('change', (e) => handleFileSelect(e.target.files));
imageInput?.addEventListener('change', (e) => handleFileSelect(e.target.files));
cameraInput?.addEventListener('change', (e) => handleFileSelect(e.target.files));
audioInput?.addEventListener('change', (e) => handleFileSelect(e.target.files));
// Глобальная функция для удаления вложения
window.removeAttachment = function(index) {
// TODO: Реализовать удаление конкретного файла
attachmentPreview.classList.add('d-none');
attachmentPreview.innerHTML = '';
documentInput.value = '';
imageInput.value = '';
cameraInput.value = '';
audioInput.value = '';
};
});
</script>
{% endblock %}