Files
WhatIDo/public/chat.html
T
dev 1740c341a0 feat(chat): правка и удаление сообщений
- PUT/DELETE /api/chat/threads/:id/messages/:messageId под requireAuth+chatLimiter,
  доступ через chatMessageFor (тред + принадлежность сообщения)
- править может только автор (админу чужое сообщение 403), удалять — автор или админ
- мягкое удаление: body='', вложения каскадом из БД + safeUnlink по path,
  счётчик непрочитанных уменьшается на 1 у своей стороны (GREATEST(col-1,0))
- refreshChatThreadPreview() пересчитывает превью треда по последнему живому сообщению
- SSE: publishChat с type, список типов валидируется через CHAT_EVENTS; новые
  message_update/message_delete подписаны в connectChatStream (admin.js)
- аудит chat.message.update с textDiff и chat.message.delete без текста
- колонки edited_at/deleted_at в db/init.sql, db/migration.sql, ensureChatTables()
  и в normalizeRestoreData + restore-INSERT — удалённые сообщения не воскресают
- фронтенд: кнопки правки/удаления по наведению, модалка правки (Ctrl+Enter),
  метка «изменено», плейсхолдер «Сообщение удалено», обновление по SSE
- покрытие в api.smoketest.js (правка/удаление/403/400/404) и backup.selftest.js
2026-10-05 18:32:58 +03:00

101 lines
4.6 KiB
HTML

<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Чаты — Админ-панель</title>
<link rel="stylesheet" href="admin.css">
</head>
<body data-page="chat">
<div class="layout">
<div class="sidebar" id="sidebar"></div>
<div class="main">
<div class="page-head">
<h2>Чаты</h2>
<p class="page-sub">Переписка тьюторов с администратором: сообщения, фотографии и документы</p>
<span class="page-status" id="authStatus"><span class="spinner dark"></span> Загрузка…</span>
<button class="head-btn" id="chatReloadBtn" type="button" title="Обновить диалоги"><i data-lucide="rotate-ccw"></i> Обновить</button>
</div>
<div class="chat-layout" id="chatLayout">
<div class="chat-side" id="chatSide">
<div class="chat-side-head">
<span class="chat-side-title">Диалоги</span>
<span class="grow"></span>
<button class="btn-primary ghost chat-new-btn" id="chatNewBtn" type="button" hidden title="Начать диалог с тьютором"><i data-lucide="user-plus"></i> Начать диалог</button>
</div>
<div class="chat-thread-list" id="chatThreadList"></div>
</div>
<div class="chat-pane" id="chatPane">
<div class="chat-empty" id="chatEmpty">
<i data-lucide="messages-square"></i>
<p class="chat-empty-title">Диалог не выбран</p>
<p class="chat-empty-sub">Выберите тьютора слева или начните новый диалог</p>
</div>
<div class="chat-main" id="chatMain" hidden>
<div class="chat-head">
<span class="chat-avatar" id="chatAvatar"></span>
<div class="chat-head-meta">
<div class="chat-head-name" id="chatPeerName"></div>
<div class="chat-head-sub" id="chatPeerSub"></div>
</div>
<span class="grow"></span>
</div>
<div class="chat-messages" id="chatMessages"></div>
<div class="chat-composer">
<div class="chat-attach-list" id="chatAttachList" hidden></div>
<div class="chat-compose-row">
<button class="chat-attach-btn" id="chatAttachBtn" type="button" title="Прикрепить файл или фото"><i data-lucide="paperclip"></i></button>
<input type="file" id="chatFileInput" multiple hidden>
<textarea class="chat-input" id="chatInput" rows="1" placeholder="Сообщение… (Ctrl+V — вставить фото)" maxlength="5000"></textarea>
<button class="btn-primary" id="chatSendBtn" type="button"><i data-lucide="send"></i> Отправить</button>
</div>
<div class="hint chat-hint" id="chatHint">Можно вставить скриншот или фото прямо из буфера обмена — Ctrl+V. Фото, документы и архивы, до 10 штук.</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal-overlay" id="chatNewModal">
<div class="edit-modal chat-new-modal">
<h3>Начать диалог</h3>
<div class="settings-field">
<label>Тьютор</label>
<select id="chatNewUser" class="settings-input"></select>
</div>
<div class="card-foot" style="justify-content:flex-end;gap:8px">
<button type="button" class="btn-primary ghost" id="chatNewCancel">Отмена</button>
<button type="submit" class="btn-primary" id="chatNewOk">Открыть диалог</button>
</div>
</div>
</div>
<div class="modal-overlay" id="chatEditModal">
<div class="edit-modal chat-edit-modal">
<h3>Редактировать сообщение</h3>
<textarea id="chatEditText" class="settings-input" maxlength="5000"></textarea>
<div class="hint">Ctrl+Enter — сохранить. Вложения сообщения останутся как есть.</div>
<div class="card-foot" style="justify-content:flex-end;gap:8px">
<button type="button" class="btn-primary ghost" id="chatEditCancel">Отмена</button>
<button type="submit" class="btn-primary" id="chatEditSave">Сохранить</button>
</div>
</div>
</div>
<div class="modal-overlay" id="imgModal">
<img id="imgModalSrc">
</div>
<div class="toast" id="toast"></div>
<script src="vendor/lucide.min.js"></script>
<script src="js/datetime.js"></script>
<script src="admin.js"></script>
<script src="js/chat.js"></script>
</body>
</html>