249 lines
10 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% extends "base.html" %}
{% block title %}Чат{% endblock %}
{% block extra_css %}
<style>
/* прибиваем низ страницы и добавляем воздух снизу */
main.main{ padding-bottom:0 !important; }
/* ===== Контейнер страницы чата ===== */
.chat-root{
--chat-top-gap: 20px;
--chat-bottom-gap: 48px;
height: calc(100dvh - var(--navbar-h));
padding: var(--chat-top-gap) 0 calc(var(--chat-bottom-gap) + env(safe-area-inset-bottom));
box-sizing: border-box;
overflow: hidden;
display: flex; flex-direction: column;
background: var(--bs-body-bg);
}
@supports(height: 100svh){
.chat-root{ height: calc(100svh - var(--navbar-h)); }
}
.chat-root > .row{ height:100%; min-height:0; }
.chat-col{ display:flex; flex-direction:column; height:100%; min-height:0; }
.feed-header{ flex:0 0 auto; }
/* ===== Карточка чата ===== */
.feed-card.chat-box{
position:relative;
flex: 1 1 auto;
min-height:0;
display:grid;
grid-template-rows: 1fr auto; /* лента + композер */
overflow: hidden;
margin-bottom:0;
background: var(--bs-body-bg);
}
/* ===== Лента с кастомным скроллом ===== */
.chat-scroll{
overflow-y:auto;
padding:.75rem;
min-height:0;
scroll-behavior:smooth;
-webkit-overflow-scrolling:touch;
scrollbar-width: thin;
scrollbar-gutter: stable both-edges;
overscroll-behavior: contain;
scrollbar-color: color-mix(in srgb, var(--bs-body-color), transparent 70%) transparent;
}
.chat-scroll:hover{ scrollbar-color: color-mix(in srgb, var(--bs-body-color), transparent 55%) transparent; }
.chat-scroll::-webkit-scrollbar{ width:10px; height:10px; }
.chat-scroll::-webkit-scrollbar-track{ background: transparent; margin-block:0; }
.chat-scroll::-webkit-scrollbar-thumb{
background-color: color-mix(in srgb, var(--bs-body-color), transparent 75%);
border-radius:999px; border:3px solid transparent; background-clip: padding-box;
}
.chat-scroll:hover::-webkit-scrollbar-thumb{
background-color: color-mix(in srgb, var(--bs-body-color), transparent 55%);
}
[data-bs-theme="dark"] .chat-scroll{
scrollbar-color: color-mix(in srgb, var(--bs-body-color), transparent 78%) transparent;
}
[data-bs-theme="dark"] .chat-scroll:hover{
scrollbar-color: color-mix(in srgb, var(--bs-body-color), transparent 60%) transparent;
}
[data-bs-theme="dark"] .chat-scroll::-webkit-scrollbar-thumb{
background-color: color-mix(in srgb, var(--bs-body-color), transparent 78%);
}
[data-bs-theme="dark"] .chat-scroll:hover::-webkit-scrollbar-thumb{
background-color: color-mix(in srgb, var(--bs-body-color), transparent 60%);
}
@media (hover:none), (pointer:coarse){
.chat-scroll{ scrollbar-width:none; }
.chat-scroll::-webkit-scrollbar{ display:none; }
}
/* ===== Композер: непрерывная линия и скругления ===== */
.chat-composer{
position: sticky; bottom:0;
border-top:none;
box-shadow: inset 0 1px 0 var(--bs-border-color);
border-bottom-left-radius: inherit;
border-bottom-right-radius: inherit;
background-clip: padding-box;
background: color-mix(in srgb, var(--bs-body-bg) 94%, transparent);
padding:.5rem .75rem calc(.5rem + env(safe-area-inset-bottom)) .75rem;
backdrop-filter: blur(8px);
z-index:1;
}
.composer-wrap{ display:flex; align-items:end; gap:.5rem; flex-wrap:nowrap; }
.composer-tools{ display:flex; gap:.25rem; align-items:center; }
/* Кнопка «вниз» */
.scroll-to-bottom{
position:absolute; right:.75rem; bottom:4.25rem;
z-index:2; display:none;
}
.scroll-to-bottom.show{ display:block; }
/* Пузыри / аватары */
.bubble{ border-radius:16px; padding:.5rem .75rem; word-break:break-word; box-shadow:0 1px 2px rgba(0,0,0,.06); }
.bubble-me{ background: color-mix(in srgb, var(--bs-primary) 92%, #fff 0%); color:#fff; }
.bubble-other{ background: var(--bs-secondary-bg); color: var(--bs-body-color); }
.mini-ava{ width:32px; height:32px; border-radius:9999px; overflow:hidden; display:grid; place-items:center; }
.mini-ava img{ width:100%; height:100%; object-fit:cover; display:block; }
/* ===== Разделители: гибкие, безопасные ===== */
.day-divider,
.unread-divider{
display:flex; align-items:center; justify-content:center; gap:.5rem;
margin:.5rem 0 1rem;
font-size:.85rem; line-height:1; font-weight:700;
pointer-events:none; user-select:none;
}
.day-divider::before, .day-divider::after,
.unread-divider::before, .unread-divider::after{
content:""; flex:1 1 auto; height:1px;
background: color-mix(in srgb, var(--bs-border-color), transparent 30%);
}
.day-divider > span, .unread-divider > span{
flex:0 0 auto; padding:0 .5rem; background: transparent;
}
.day-divider{ color: var(--bs-secondary-color); }
.unread-divider{ color: var(--bs-danger); }
</style>
{% endblock %}
{% block content %}
<div class="container chat-root">
<div class="row justify-content-center">
<div class="col-lg-9 col-xl-8 chat-col">
<!-- Заголовок -->
<div class="feed-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="feed-card chat-box">
<!-- Кнопка «вниз» -->
<button id="scrollBtn" class="btn btn-light shadow scroll-to-bottom" type="button" aria-label="Прокрутить вниз">
<i class="bi-arrow-down-circle"></i>
</button>
<!-- Лента сообщений -->
<div id="chatScroll"
class="chat-scroll"
data-me-id="{{ user.id }}"
data-chat-id="{{ chat.id }}"
data-last-read-ts="{% if last_read_at %}{{ last_read_at|date:'U' }}000{% endif %}">
{% 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 %}">
{{ message.content|linebreaksbr }}
</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 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>
<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>
</form>
</div>
</article>
</div>
</div>
</div>
{% endblock %}