Files
ACORE/templates/pages/chat.html
T
lokses 8de1ffaa34 i18n: перевод страницы чата (ru база, uk/en)
- Шаблон chat.html → русская база с {% trans %}/{% blocktrans %}
- JS-строки chat.js → gettext() + JavaScriptCatalog (/jsi18n/), подключён
  в index.html; защитный fallback если каталог не загрузился
- Переводы uk/en для обоих доменов (django + djangojs), скомпилированы
- makemessages заодно извлёк непереведённые строки прошлых фич (2FA,
  nextcloud, документы) — оставлены пустыми (рендерятся на базовом ru)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-28 22:00:35 +03:00

163 lines
15 KiB
HTML

{% load i18n %}
<div class="page-content">
<div style="margin-bottom:1.5rem;">
<h1 style="font-size:1.25rem; font-weight:500; color:var(--color-foreground);">{% trans "Чат" %}</h1>
<p style="font-size:0.875rem; color:var(--color-muted-foreground); margin-top:0.25rem;">{% trans "Внутренний корпоративный мессенджер (Mattermost)" %}</p>
</div>
{% if chat_state == 'ready' %}
{{ chat_config|json_script:"chat-config-data" }}
{{ chat_colleagues|json_script:"chat-colleagues-data" }}
<!-- Поиск по всем сообщениям (над чатом) -->
<div style="display:flex; gap:0.5rem; align-items:center; margin-bottom:1rem;">
<input type="text" id="chat-search-input" placeholder="{% trans 'Поиск по всем сообщениям...' %}" class="input" style="flex:1; max-width:34rem;"
onkeydown="if(event.key==='Enter'){event.preventDefault();chatSearchMessages(this.value);}">
<button type="button" class="btn btn-secondary btn-icon" aria-label="{% trans 'Искать' %}"
onclick="chatSearchMessages(document.getElementById('chat-search-input').value)">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
</button>
</div>
<!-- Переключатель «комнат» (команды Mattermost: Офис / Аптеки / Склад) -->
<div id="chat-team-switcher" style="margin-bottom:1rem;"></div>
<div id="chat-app" style="display:grid; grid-template-columns:260px 1fr; gap:1.25rem; height:calc(100vh - 15rem); max-height:640px;">
<div style="background:var(--color-card); border-radius:0.75rem; border:1px solid var(--color-border); box-shadow:0 1px 2px rgb(0 0 0/0.05); display:flex; flex-direction:column; overflow:hidden;">
<div style="flex:1; overflow-y:auto; padding-bottom:0.5rem;">
<div style="display:flex; align-items:center; justify-content:space-between; padding:0.75rem 0.75rem 0.375rem;">
<span style="font-size:0.6875rem; font-weight:600; letter-spacing:0.06em; text-transform:uppercase; color:var(--color-muted-foreground);">{% trans "Личные сообщения" %}</span>
<button type="button" class="btn btn-ghost btn-icon" style="width:1.5rem; height:1.5rem;" onclick="chatOpenNewDmPicker()" aria-label="{% trans 'Новое сообщение' %}">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" x2="12" y1="5" y2="19"/><line x1="5" x2="19" y1="12" y2="12"/></svg>
</button>
</div>
<div id="chat-dm-list">
<div style="padding:0.5rem 0.75rem; font-size:0.75rem; color:var(--color-muted-foreground);">{% trans "Загрузка…" %}</div>
</div>
<div style="padding:0.75rem 0.75rem 0.375rem; margin-top:0.25rem;">
<span style="font-size:0.6875rem; font-weight:600; letter-spacing:0.06em; text-transform:uppercase; color:var(--color-muted-foreground);">{% trans "Каналы" %}</span>
</div>
<div id="chat-channel-list">
<div style="padding:0.5rem 0.75rem; font-size:0.8125rem; color:var(--color-muted-foreground);">{% trans "Загрузка…" %}</div>
</div>
</div>
<div style="border-top:1px solid var(--color-border); padding:0.5rem 0.75rem;">
<label style="display:flex; align-items:center; gap:0.375rem; font-size:0.75rem; color:var(--color-muted-foreground); cursor:pointer;">
<input type="checkbox" id="chat-show-archived" onchange="chatToggleArchived(this.checked)" style="cursor:pointer;">
{% trans "Показывать закрытые разговоры" %}
</label>
</div>
</div>
<div style="background:var(--color-card); border-radius:0.75rem; border:1px solid var(--color-border); box-shadow:0 1px 2px rgb(0 0 0/0.05); display:flex; flex-direction:column; overflow:hidden;">
<div id="chat-header" style="padding:0.875rem 1.25rem; border-bottom:1px solid var(--color-border); display:flex; align-items:center; gap:0.75rem;">
<span style="font-size:0.875rem; color:var(--color-muted-foreground);">{% trans "Выберите канал слева" %}</span>
</div>
<div id="chat-messages" style="flex:1; overflow-y:auto; padding:1.25rem; display:flex; flex-direction:column; gap:1rem;"></div>
<div id="chat-search-results" style="display:none; flex:1; overflow-y:auto; padding:0.875rem 1.25rem; flex-direction:column; gap:0.5rem;"></div>
<div id="chat-composer-files" style="display:none; padding:0 1.25rem; flex-wrap:wrap; gap:0.375rem;"></div>
<div style="padding:0.875rem 1.25rem; border-top:1px solid var(--color-border); display:flex; gap:0.5rem; align-items:flex-end;">
<input type="file" id="chat-composer-file-input" multiple style="display:none;" onchange="chatComposerFilesSelected(this)">
<button type="button" class="btn btn-ghost btn-icon" disabled id="chat-composer-attach" onclick="document.getElementById('chat-composer-file-input').click()" aria-label="{% trans 'Прикрепить файл' %}">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>
</button>
<textarea id="chat-composer-input" placeholder="{% trans 'Написать сообщение...' %}" disabled
style="flex:1; min-height:2.5rem; max-height:8rem; resize:none; background:var(--color-muted); border:1px solid var(--color-input); border-radius:0.625rem; padding:0.5rem 0.875rem; font-size:0.875rem; color:var(--color-foreground); font-family:var(--font-sans); outline:none; transition:border-color 150ms; box-sizing:border-box;"
onfocus="this.style.borderColor='var(--color-ring)'" onblur="this.style.borderColor=''"></textarea>
<button id="chat-composer-send" type="button" class="btn btn-primary btn-icon" disabled onclick="chatSendMessage()">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m22 2-7 20-4-9-9-4Z"/><path d="M22 2 11 13"/></svg>
</button>
</div>
</div>
</div>
<!-- Выездная панель: обсуждение -->
<div id="chat-thread-panel-overlay" onclick="chatPanelClose('chat-thread-panel')" style="display:none; position:fixed; inset:0; background:rgba(0,0,0,0.4); z-index:1000;"></div>
<div id="chat-thread-panel" style="position:fixed; top:0; right:0; height:100vh; width:26rem; max-width:92vw; background:var(--color-card); box-shadow:-8px 0 24px rgba(0,0,0,0.18); z-index:1001; transform:translateX(100%); transition:transform 250ms ease; overflow-y:auto; display:flex; flex-direction:column;">
<div style="padding:1rem 1.25rem; border-bottom:1px solid var(--color-border); display:flex; align-items:center; justify-content:space-between; position:sticky; top:0; background:var(--color-card); z-index:1;">
<span style="font-size:0.9375rem; font-weight:600; color:var(--color-foreground);">{% trans "Обсуждение" %}</span>
<button type="button" onclick="chatPanelClose('chat-thread-panel')" class="btn btn-ghost btn-icon" aria-label="{% trans 'Закрыть' %}">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 6 6 18M6 6l12 12"/></svg>
</button>
</div>
<div id="chat-thread-messages" style="flex:1; overflow-y:auto; padding:1.25rem; display:flex; flex-direction:column; gap:1rem;"></div>
<div id="chat-thread-composer-files" style="display:none; padding:0 1.25rem; flex-wrap:wrap; gap:0.375rem;"></div>
<div style="padding:0.875rem 1.25rem; border-top:1px solid var(--color-border); display:flex; gap:0.5rem; align-items:flex-end;">
<input type="file" id="chat-thread-composer-file-input" multiple style="display:none;" onchange="chatThreadComposerFilesSelected(this)">
<button type="button" class="btn btn-ghost btn-icon" onclick="document.getElementById('chat-thread-composer-file-input').click()" aria-label="{% trans 'Прикрепить файл' %}">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>
</button>
<textarea id="chat-thread-composer-input" placeholder="{% trans 'Ответить в обсуждении...' %}"
style="flex:1; min-height:2.5rem; max-height:8rem; resize:none; background:var(--color-muted); border:1px solid var(--color-input); border-radius:0.625rem; padding:0.5rem 0.875rem; font-size:0.875rem; color:var(--color-foreground); font-family:var(--font-sans); outline:none; box-sizing:border-box;"></textarea>
<button type="button" class="btn btn-primary btn-icon" onclick="chatThreadSendReply()">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m22 2-7 20-4-9-9-4Z"/><path d="M22 2 11 13"/></svg>
</button>
</div>
</div>
<!-- Выездная панель: новое личное/групповое сообщение -->
<div id="chat-dm-picker-panel-overlay" onclick="chatPanelClose('chat-dm-picker-panel')" style="display:none; position:fixed; inset:0; background:rgba(0,0,0,0.4); z-index:1000;"></div>
<div id="chat-dm-picker-panel" style="position:fixed; top:0; right:0; height:100vh; width:26rem; max-width:92vw; background:var(--color-card); box-shadow:-8px 0 24px rgba(0,0,0,0.18); z-index:1001; transform:translateX(100%); transition:transform 250ms ease; overflow-y:auto; display:flex; flex-direction:column;">
<div style="padding:1rem 1.25rem; border-bottom:1px solid var(--color-border); display:flex; align-items:center; justify-content:space-between; position:sticky; top:0; background:var(--color-card); z-index:1;">
<span style="font-size:0.9375rem; font-weight:600; color:var(--color-foreground);">{% trans "Новое сообщение" %}</span>
<button type="button" onclick="chatPanelClose('chat-dm-picker-panel')" class="btn btn-ghost btn-icon" aria-label="{% trans 'Закрыть' %}">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 6 6 18M6 6l12 12"/></svg>
</button>
</div>
<div style="padding:1.25rem; display:flex; flex-direction:column; gap:1rem; flex:1;">
<input type="text" id="chat-dm-picker-search" placeholder="{% trans 'Поиск коллег...' %}" class="input" style="width:100%;" oninput="chatRenderDmPickerList()">
<div id="chat-dm-picker-list" style="display:flex; flex-direction:column; gap:0.25rem; overflow-y:auto; flex:1;"></div>
<button type="button" class="btn btn-primary" style="width:100%; justify-content:center;" onclick="chatStartNewDm()">{% trans "Начать разговор" %}</button>
</div>
</div>
<script>chatInit();</script>
{% elif chat_state == 'not_synced' %}
<div style="background:var(--color-card); border-radius:0.75rem; border:1px solid var(--color-border); box-shadow:0 1px 2px rgb(0 0 0/0.05); padding:2.5rem; text-align:center;">
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="var(--color-muted-foreground)" stroke-width="1.5" style="margin:0 auto 0.75rem;">
<path d="M21 12a9 9 0 1 1-2.64-6.36"/><path d="M21 3v6h-6"/>
</svg>
<div style="font-size:0.9375rem; font-weight:500; color:var(--color-foreground); margin-bottom:0.375rem;">{% trans "Ваша учётная запись ещё не синхронизирована с Mattermost" %}</div>
<p style="font-size:0.8125rem; color:var(--color-muted-foreground); max-width:24rem; margin:0 auto 1rem;">
{% trans "Нажмите кнопку ниже, чтобы создать или привязать вашу учётную запись в Mattermost." %}
</p>
<button type="button" class="btn btn-primary" hx-post="/chat/sync/" hx-target="#main-content" hx-indicator="#loader">
{% trans "Синхронизировать сейчас" %}
</button>
</div>
{% elif chat_state == 'token_error' %}
<div style="background:var(--color-card); border-radius:0.75rem; border:1px solid var(--color-border); box-shadow:0 1px 2px rgb(0 0 0/0.05); padding:2.5rem; text-align:center;">
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="var(--color-destructive)" stroke-width="1.5" style="margin:0 auto 0.75rem;">
<circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/>
</svg>
<div style="font-size:0.9375rem; font-weight:500; color:var(--color-foreground); margin-bottom:0.375rem;">{% trans "Не удалось получить доступ к Mattermost" %}</div>
<p style="font-size:0.8125rem; color:var(--color-muted-foreground); max-width:24rem; margin:0 auto;">
{% trans "Обратитесь в IT-отдел — возможно, отключена выдача Personal Access Token на сервере Mattermost." %}
</p>
</div>
{% else %}
<div style="background:var(--color-card); border-radius:0.75rem; border:1px solid var(--color-border); box-shadow:0 1px 2px rgb(0 0 0/0.05); padding:2.5rem; text-align:center;">
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="var(--color-muted-foreground)" stroke-width="1.5" style="margin:0 auto 0.75rem;">
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
</svg>
<div style="font-size:0.9375rem; font-weight:500; color:var(--color-foreground); margin-bottom:0.375rem;">{% trans "Чат ещё не подключён" %}</div>
<p style="font-size:0.8125rem; color:var(--color-muted-foreground); max-width:28rem; margin:0 auto;">
{% blocktrans %}Укажите публичный URL Mattermost (поле <code>public_url</code> в «Дополнительные параметры» коннектора Mattermost в админке) — и он появится здесь.{% endblocktrans %}
</p>
</div>
{% endif %}
</div>