- Заменен лишний div.container на стандартный container для единообразия - Заменен класс .card на .ios-card в секции активности для устранения конфликтов стилей - Добавлен card-list.css в base.html для корректного отображения карточек новостей - Добавлены CSS правила для правильного поведения .row и колонок в grid-layout - Исправлена проблема с выходом календаря за границы страницы - Убраны видимые скроллбары у modal-body и modal-content для чистого UI
265 lines
13 KiB
HTML
265 lines
13 KiB
HTML
{% extends "base.html" %}
|
||
{% load static %}
|
||
|
||
{% block title %}Чат{% endblock %}
|
||
|
||
{% block extra_css %}{% endblock %}
|
||
|
||
{% block content %}
|
||
<div class="container chat-root">
|
||
<div class="row gx-0">
|
||
<div class="col-12 chat-col">
|
||
|
||
<!-- Заголовок -->
|
||
<div class="section-header">
|
||
{% if chat.type == 'global' %}
|
||
<i class="bi-globe fs-2 text-primary"></i>
|
||
<h2 class="title mb-0">Глобальный чат</h2>
|
||
{% elif chat.type == 'department' %}
|
||
<i class="bi-diagram-3 fs-2 text-primary"></i>
|
||
<h2 class="title mb-0">Чат отдела: {{ chat.department.name }}</h2>
|
||
{% else %}
|
||
<i class="bi-person fs-2 text-primary"></i>
|
||
<h2 class="title mb-0">
|
||
{% for u in chat.participants.all %}
|
||
{% if u != user %}
|
||
{% if not forloop.first %}, {% endif %}
|
||
<a href="{% url 'employees:employee_detail' u.pk %}" class="text-decoration-none">
|
||
{{ u.get_full_name|default:u.username }}
|
||
</a>
|
||
{% endif %}
|
||
{% endfor %}
|
||
</h2>
|
||
{% endif %}
|
||
<div class="ms-auto">
|
||
<a href="{% url 'communications:chat_list' %}" class="btn btn-ghost">
|
||
<i class="bi-arrow-left"></i> К чатам
|
||
</a>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Корпус чата -->
|
||
<article class="card chat-box">
|
||
|
||
<!-- Кнопка «вниз» -->
|
||
<button id="scrollBtn" class="scroll-to-bottom" type="button" aria-label="Прокрутить вниз">
|
||
<i class="bi-arrow-down"></i>
|
||
</button>
|
||
|
||
<!-- Лента сообщений -->
|
||
<div id="chatScroll"
|
||
class="chat-scroll"
|
||
data-me-id="{{ user.id }}"
|
||
data-me-avatar="{% if user.avatar %}{{ user.avatar.url }}{% endif %}"
|
||
data-profile-url="{% url 'employees:profile' %}"
|
||
data-detail-url-template="{% url 'employees:employee_detail' 0 %}"
|
||
data-chat-id="{{ chat.id }}"
|
||
data-fetch-url="{% url 'api:v1:chat_messages' chat.pk %}"
|
||
data-page-size="{{ messages_page_size }}"
|
||
data-oldest-id="{{ messages_oldest_id }}"
|
||
data-oldest-ts="{{ messages_oldest_ts }}"
|
||
data-has-more="{% if messages_has_more %}1{% else %}0{% endif %}"
|
||
data-last-read-ts="{% if last_read_at %}{{ last_read_at|date:'U' }}000{% endif %}">
|
||
<div class="history-loader d-none" data-history-loader>
|
||
<div class="spinner-border spinner-border-sm text-secondary" role="status" aria-hidden="true"></div>
|
||
<span class="small">Загружаем ранние сообщения…</span>
|
||
</div>
|
||
{% if messages %}
|
||
{% for message in messages %}
|
||
{% ifchanged message.created_at|date:"d.m.Y" %}
|
||
<div class="day-divider"><span>{{ message.created_at|date:"d.m.Y" }}</span></div>
|
||
{% endifchanged %}
|
||
|
||
<!-- сообщение -->
|
||
<div class="d-flex mb-3 msg {% if message.author_id == user.id %}justify-content-end{% else %}justify-content-start{% endif %}"
|
||
data-id="{{ message.id }}"
|
||
data-ts="{{ message.created_at|date:'U' }}000"
|
||
data-author-id="{{ message.author_id }}">
|
||
{% if message.author_id != user.id %}
|
||
<a class="me-2 text-decoration-none" href="{% url 'employees:employee_detail' message.author.pk %}">
|
||
<span class="mini-ava border">
|
||
{% if message.author.avatar %}<img src="{{ message.author.avatar.url }}" alt="" loading="lazy">
|
||
{% else %}<i class="bi-person"></i>{% endif %}
|
||
</span>
|
||
</a>
|
||
{% endif %}
|
||
|
||
<div class="d-flex flex-column" style="max-width:80%;">
|
||
<div class="small text-secondary {% if message.author_id == user.id %}text-end{% endif %}">
|
||
{% if message.author_id == user.id %}
|
||
<a href="{% url 'employees:profile' %}" class="text-decoration-none">Вы</a>
|
||
{% else %}
|
||
<a href="{% url 'employees:employee_detail' message.author.pk %}" class="text-decoration-none fw-semibold">
|
||
{{ message.author.get_full_name|default:message.author.username }}
|
||
</a>
|
||
{% endif %}
|
||
· <time datetime="{{ message.created_at|date:'c' }}">{{ message.created_at|date:"H:i" }}</time>
|
||
</div>
|
||
<div class="mt-1 bubble {% if message.author_id == user.id %}bubble-me{% else %}bubble-other{% endif %}">
|
||
{% if message.content %}
|
||
{{ message.content|linebreaksbr }}
|
||
{% endif %}
|
||
|
||
{% if message.has_attachments %}
|
||
<div class="message-attachments mt-2">
|
||
{% for attachment in message.attachments.all %}
|
||
<div class="attachment-item mb-2">
|
||
{% if attachment.file_type == 'image' %}
|
||
<a href="{{ attachment.file.url }}" target="_blank" class="d-inline-block">
|
||
<img src="{{ attachment.file.url }}"
|
||
alt="{{ attachment.file_name }}"
|
||
loading="lazy"
|
||
class="chat-media chat-media--image">
|
||
</a>
|
||
{% elif attachment.file_type == 'audio' %}
|
||
<audio controls class="w-100">
|
||
<source src="{{ attachment.file.url }}" type="{{ attachment.mime_type }}">
|
||
</audio>
|
||
<div class="small text-secondary">{{ attachment.file_name }}</div>
|
||
{% elif attachment.file_type == 'video' %}
|
||
<video controls preload="metadata" playsinline class="chat-media chat-media--video">
|
||
<source src="{{ attachment.file.url }}" type="{{ attachment.mime_type }}">
|
||
Ваш браузер не поддерживает видео.
|
||
</video>
|
||
<div class="small text-secondary">{{ attachment.file_name }}</div>
|
||
{% else %}
|
||
<a href="{{ attachment.file.url }}"
|
||
class="d-flex align-items-center text-decoration-none p-2 border rounded"
|
||
download="{{ attachment.file_name }}">
|
||
<i class="bi-file-earmark fs-3 text-primary me-2"></i>
|
||
<div>
|
||
<div class="fw-semibold">{{ attachment.file_name }}</div>
|
||
<div class="small text-secondary">{{ attachment.file_size|filesizeformat }}</div>
|
||
</div>
|
||
</a>
|
||
{% endif %}
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
|
||
{% if message.author_id == user.id %}
|
||
<a class="ms-2 text-decoration-none" href="{% url 'employees:profile' %}">
|
||
<span class="mini-ava border">
|
||
{% if user.avatar %}<img src="{{ user.avatar.url }}" alt="" loading="lazy">
|
||
{% else %}<i class="bi-person"></i>{% endif %}
|
||
</span>
|
||
</a>
|
||
{% endif %}
|
||
</div>
|
||
{% endfor %}
|
||
{% else %}
|
||
<div class="text-center text-secondary py-5">Пока нет сообщений.</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<!-- Композер -->
|
||
<div class="chat-composer">
|
||
<div id="typing" class="small text-secondary ps-1 pt-1 d-none">Собеседник печатает…</div>
|
||
<form id="chatForm"
|
||
class="composer-wrap"
|
||
method="post"
|
||
action="{% url 'communications:chat_detail' chat.pk %}"
|
||
enctype="multipart/form-data"
|
||
data-chat-composer="1"
|
||
data-chat-id="{{ chat.id }}"
|
||
data-upload-url="{% url 'api:v1:upload_message' %}"
|
||
data-me-id="{{ user.id }}"
|
||
data-me-name="{{ user.get_full_name|default:user.username }}"
|
||
data-me-avatar="{% if user.avatar %}{{ user.avatar.url }}{% endif %}"
|
||
data-profile-url="{% url 'employees:profile' %}"
|
||
data-detail-url-template="{% url 'employees:employee_detail' 0 %}">
|
||
{% csrf_token %}
|
||
<div class="message-field">
|
||
<!-- Кнопка вложений с выпадающим меню -->
|
||
<div class="dropdown message-attach">
|
||
<button type="button" class="btn btn-ghost btn-attachment message-icon-btn"
|
||
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>
|
||
|
||
<!-- Кнопка выбора эмодзи -->
|
||
<div class="dropdown message-emoji">
|
||
<button type="button" class="btn btn-ghost btn-emoji message-icon-btn"
|
||
id="emojiDropdown"
|
||
data-bs-toggle="dropdown"
|
||
data-emoji-toggle="1"
|
||
aria-expanded="false"
|
||
title="Вставить смайлик">
|
||
<i class="bi-emoji-smile"></i>
|
||
</button>
|
||
<div class="dropdown-menu dropdown-menu-start message-emoji-menu" aria-labelledby="emojiDropdown">
|
||
<emoji-picker data-emoji-picker class="chat-emoji-picker"></emoji-picker>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Поле ввода текста -->
|
||
<textarea name="content"
|
||
id="id_content"
|
||
class="form-control composer-textarea message-input"
|
||
rows="1"
|
||
placeholder="Напишите сообщение…"></textarea>
|
||
|
||
<!-- Кнопка отправки -->
|
||
<button type="submit" class="btn btn-primary btn-send message-send" title="Отправить">
|
||
<i class="bi-send-fill"></i>
|
||
</button>
|
||
</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 id="attachmentPreview" class="attachment-preview d-none"></div>
|
||
</form>
|
||
</div>
|
||
</article>
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{% endblock %}
|
||
|
||
{% block extra_js %}
|
||
{{ block.super }}
|
||
<script type="module" src="https://cdn.jsdelivr.net/npm/emoji-picker-element@^1/index.js"></script>
|
||
<script type="module" src="{% static 'js/components/chatComposer.js' %}"></script>
|
||
<script type="module" src="{% static 'js/components/chatHistoryLoader.js' %}"></script>
|
||
{% endblock %}
|