Redesign: современный интерфейс чата в стиле мессенджеров
Изменения в chat_detail.html: - Убрана кнопка с фотографией из композера - Кнопка вложений встроена в строку ввода с выпадающим меню - Добавлено выпадающее меню с опциями: Документ, Фото/видео, Камера, Аудио - Добавлены скрытые input для каждого типа файлов - Реализовано превью прикрепленных файлов - Добавлен JavaScript для обработки выбора и отображения файлов Изменения в chat-detail.css: - Обновлен стиль композера под современные мессенджеры - Круглые кнопки вложений и отправки (40px) - Поле ввода с закругленными углами (border-radius: 20px) - Анимации hover для кнопок (scale 1.05) - Стили для превью вложений - Улучшенное выпадающее меню с иконками и transition эффектами
This commit is contained in:
parent
55a450e30c
commit
64e23e32ec
@ -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);
|
||||
|
||||
@ -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 %}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user