2696 lines
92 KiB
HTML
2696 lines
92 KiB
HTML
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||
<title>Secure Chat App</title>
|
||
<style>
|
||
:root {
|
||
--bg-primary: #ffffff;
|
||
--bg-secondary: #f8f9fa;
|
||
--bg-tertiary: #e9ecef;
|
||
--text-primary: #2c3e50;
|
||
--text-secondary: #7f8c8d;
|
||
--border-color: #e9ecef;
|
||
--primary-color: #3498db;
|
||
--primary-hover: #2980b9;
|
||
--success-color: #27ae60;
|
||
--error-color: #e74c3c;
|
||
--warning-color: #f39c12;
|
||
--shadow: rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
[data-theme="dark"] {
|
||
--bg-primary: #1a1a1a;
|
||
--bg-secondary: #2d2d2d;
|
||
--bg-tertiary: #3d3d3d;
|
||
--text-primary: #ffffff;
|
||
--text-secondary: #b0b0b0;
|
||
--border-color: #444444;
|
||
--primary-color: #2980b9;
|
||
--primary-hover: #3498db;
|
||
--success-color: #2ecc71;
|
||
--error-color: #e74c3c;
|
||
--warning-color: #f39c12;
|
||
--shadow: rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
body {
|
||
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
height: 100vh;
|
||
overflow: hidden;
|
||
transition: background 0.3s ease;
|
||
}
|
||
|
||
[data-theme="dark"] body {
|
||
background: linear-gradient(135deg, #2c3e50 0%, #3498db 100%);
|
||
}
|
||
|
||
/* Auth Container */
|
||
.auth-container {
|
||
width: 100%;
|
||
max-width: 400px;
|
||
margin: 0 auto;
|
||
background: var(--bg-primary);
|
||
padding: 30px 20px;
|
||
border-radius: 0;
|
||
box-shadow: 0 0 0 rgba(0, 0, 0, 0);
|
||
height: 100vh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.auth-tabs {
|
||
display: flex;
|
||
margin-bottom: 25px;
|
||
border-bottom: 1px solid var(--border-color);
|
||
}
|
||
|
||
.auth-tab {
|
||
flex: 1;
|
||
padding: 15px 10px;
|
||
text-align: center;
|
||
cursor: pointer;
|
||
border-bottom: 3px solid transparent;
|
||
transition: all 0.3s ease;
|
||
font-size: 14px;
|
||
color: var(--text-primary);
|
||
}
|
||
|
||
.auth-tab.active {
|
||
border-bottom-color: var(--primary-color);
|
||
color: var(--primary-color);
|
||
font-weight: 600;
|
||
}
|
||
|
||
.auth-form {
|
||
display: none;
|
||
}
|
||
|
||
.auth-form.active {
|
||
display: block;
|
||
}
|
||
|
||
.form-group {
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.form-group label {
|
||
display: block;
|
||
margin-bottom: 6px;
|
||
font-weight: 500;
|
||
color: var(--text-primary);
|
||
font-size: 14px;
|
||
}
|
||
|
||
.form-control {
|
||
width: 100%;
|
||
padding: 12px 15px;
|
||
border: 2px solid var(--border-color);
|
||
border-radius: 8px;
|
||
font-size: 16px;
|
||
outline: none;
|
||
transition: border-color 0.3s ease;
|
||
background: var(--bg-primary);
|
||
color: var(--text-primary);
|
||
}
|
||
|
||
.form-control:focus {
|
||
border-color: var(--primary-color);
|
||
}
|
||
|
||
.btn {
|
||
width: 100%;
|
||
padding: 12px;
|
||
background: linear-gradient(
|
||
135deg,
|
||
var(--primary-color),
|
||
var(--primary-hover)
|
||
);
|
||
color: white;
|
||
border: none;
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.btn:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 5px 15px rgba(52, 152, 219, 0.4);
|
||
}
|
||
|
||
.alert {
|
||
padding: 10px 12px;
|
||
border-radius: 6px;
|
||
margin-bottom: 15px;
|
||
display: none;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.alert-success {
|
||
background: #d4edda;
|
||
color: #155724;
|
||
border: 1px solid #c3e6cb;
|
||
}
|
||
|
||
.alert-error {
|
||
background: #f8d7da;
|
||
color: #721c24;
|
||
border: 1px solid #f5c6cb;
|
||
}
|
||
|
||
/* Chat Container */
|
||
.chat-container {
|
||
width: 100%;
|
||
height: 100vh;
|
||
background: var(--bg-primary);
|
||
display: none;
|
||
flex-direction: column;
|
||
transition: background 0.3s ease;
|
||
}
|
||
|
||
/* Mobile Header */
|
||
.mobile-header {
|
||
display: none;
|
||
padding: 15px;
|
||
background: var(--bg-secondary);
|
||
color: var(--text-primary);
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
border-bottom: 1px solid var(--border-color);
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 100;
|
||
}
|
||
|
||
.menu-btn {
|
||
background: none;
|
||
border: none;
|
||
color: var(--text-primary);
|
||
font-size: 20px;
|
||
cursor: pointer;
|
||
padding: 5px;
|
||
}
|
||
|
||
.mobile-title {
|
||
font-weight: 600;
|
||
font-size: 18px;
|
||
}
|
||
|
||
.profile-header-btn {
|
||
background: none;
|
||
border: none;
|
||
cursor: pointer;
|
||
padding: 5px;
|
||
border-radius: 50%;
|
||
transition: background 0.3s ease;
|
||
}
|
||
|
||
.profile-header-btn:hover {
|
||
background: var(--bg-tertiary);
|
||
}
|
||
|
||
.profile-header-avatar {
|
||
width: 35px;
|
||
height: 35px;
|
||
border-radius: 50%;
|
||
object-fit: cover;
|
||
background: var(--bg-tertiary);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: var(--text-secondary);
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.profile-header-avatar img {
|
||
width: 100%;
|
||
height: 100%;
|
||
border-radius: 50%;
|
||
object-fit: cover;
|
||
}
|
||
|
||
.chats-panel {
|
||
width: 350px;
|
||
background: var(--bg-secondary);
|
||
color: var(--text-primary);
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: 100vh;
|
||
transition: transform 0.3s ease;
|
||
border-right: 1px solid var(--border-color);
|
||
}
|
||
|
||
.chats-header {
|
||
padding: 20px;
|
||
border-bottom: 1px solid var(--border-color);
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.chats-tabs {
|
||
display: flex;
|
||
border-bottom: 1px solid var(--border-color);
|
||
}
|
||
|
||
.chat-tab {
|
||
flex: 1;
|
||
padding: 15px 10px;
|
||
text-align: center;
|
||
cursor: pointer;
|
||
border-bottom: 3px solid transparent;
|
||
transition: all 0.3s ease;
|
||
font-size: 14px;
|
||
color: var(--text-primary);
|
||
}
|
||
|
||
.chat-tab.active {
|
||
border-bottom-color: var(--primary-color);
|
||
color: var(--primary-color);
|
||
font-weight: 600;
|
||
}
|
||
|
||
.search-container {
|
||
padding: 15px;
|
||
border-bottom: 1px solid var(--border-color);
|
||
display: none;
|
||
}
|
||
|
||
.search-input {
|
||
width: 100%;
|
||
padding: 10px 12px;
|
||
border: 1px solid var(--border-color);
|
||
border-radius: 20px;
|
||
background: var(--bg-tertiary);
|
||
color: var(--text-primary);
|
||
outline: none;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.search-input::placeholder {
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
.search-results {
|
||
max-height: 300px;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.chat-list {
|
||
list-style: none;
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.chat-item {
|
||
padding: 12px 15px;
|
||
border-bottom: 1px solid var(--border-color);
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.chat-item:hover {
|
||
background: var(--bg-tertiary);
|
||
}
|
||
|
||
.chat-item.active {
|
||
background: var(--primary-color);
|
||
color: white;
|
||
}
|
||
|
||
.chat-avatar {
|
||
width: 45px;
|
||
height: 45px;
|
||
border-radius: 50%;
|
||
margin-right: 12px;
|
||
object-fit: cover;
|
||
background: var(--bg-tertiary);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: var(--text-secondary);
|
||
font-size: 18px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.chat-avatar img {
|
||
width: 100%;
|
||
height: 100%;
|
||
border-radius: 50%;
|
||
object-fit: cover;
|
||
}
|
||
|
||
.chat-info {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.chat-username {
|
||
font-weight: 600;
|
||
margin-bottom: 3px;
|
||
font-size: 14px;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.chat-phone {
|
||
font-size: 11px;
|
||
color: var(--text-secondary);
|
||
margin-bottom: 3px;
|
||
}
|
||
|
||
.chat-last-message {
|
||
font-size: 12px;
|
||
color: var(--text-secondary);
|
||
opacity: 0.8;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.chat-meta {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-end;
|
||
flex-shrink: 0;
|
||
margin-left: 10px;
|
||
}
|
||
|
||
.chat-time {
|
||
font-size: 10px;
|
||
color: var(--text-secondary);
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.unread-badge {
|
||
background: var(--error-color);
|
||
color: white;
|
||
border-radius: 50%;
|
||
width: 18px;
|
||
height: 18px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 10px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.chat-panel {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
background: var(--bg-primary);
|
||
height: 100vh;
|
||
position: relative;
|
||
}
|
||
|
||
.chat-header {
|
||
padding: 12px 15px;
|
||
background: var(--bg-primary);
|
||
border-bottom: 1px solid var(--border-color);
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 10;
|
||
min-height: 64px;
|
||
}
|
||
|
||
.chat-header-info {
|
||
display: flex;
|
||
align-items: center;
|
||
flex: 1;
|
||
}
|
||
|
||
.back-btn {
|
||
display: none;
|
||
background: none;
|
||
border: none;
|
||
font-size: 18px;
|
||
margin-right: 10px;
|
||
cursor: pointer;
|
||
color: var(--primary-color);
|
||
}
|
||
|
||
.chat-header-avatar {
|
||
width: 40px;
|
||
height: 40px;
|
||
border-radius: 50%;
|
||
margin-right: 10px;
|
||
object-fit: cover;
|
||
background: var(--bg-tertiary);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: var(--text-secondary);
|
||
font-size: 16px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.chat-header-avatar img {
|
||
width: 100%;
|
||
height: 100%;
|
||
border-radius: 50%;
|
||
object-fit: cover;
|
||
}
|
||
|
||
.chat-header-text {
|
||
display: flex;
|
||
flex-direction: column;
|
||
min-width: 0;
|
||
flex: 1;
|
||
}
|
||
|
||
.chat-header-username {
|
||
font-weight: 600;
|
||
font-size: 16px;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
color: var(--text-primary);
|
||
}
|
||
|
||
.chat-header-phone {
|
||
font-size: 12px;
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
.chat-messages {
|
||
flex: 1;
|
||
padding: 15px;
|
||
overflow-y: auto;
|
||
background: var(--bg-primary);
|
||
display: flex;
|
||
flex-direction: column;
|
||
min-height: 0;
|
||
}
|
||
|
||
.message {
|
||
max-width: 85%;
|
||
margin: 8px 0;
|
||
padding: 12px 15px;
|
||
border-radius: 18px;
|
||
position: relative;
|
||
word-wrap: break-word;
|
||
overflow-wrap: break-word;
|
||
word-break: break-word;
|
||
animation: fadeIn 0.3s ease;
|
||
}
|
||
|
||
@keyframes fadeIn {
|
||
from {
|
||
opacity: 0;
|
||
transform: translateY(10px);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
}
|
||
|
||
.incoming {
|
||
background: var(--bg-secondary);
|
||
border: 1px solid var(--border-color);
|
||
align-self: flex-start;
|
||
border-bottom-left-radius: 5px;
|
||
box-shadow: 0 2px 5px var(--shadow);
|
||
color: var(--text-primary);
|
||
}
|
||
|
||
.outgoing {
|
||
background: linear-gradient(
|
||
135deg,
|
||
var(--primary-color),
|
||
var(--primary-hover)
|
||
);
|
||
color: white;
|
||
align-self: flex-end;
|
||
margin-left: auto;
|
||
border-bottom-right-radius: 5px;
|
||
box-shadow: 0 2px 5px rgba(52, 152, 219, 0.3);
|
||
}
|
||
|
||
.message-sender {
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
margin-bottom: 4px;
|
||
opacity: 0.8;
|
||
}
|
||
|
||
.message-content {
|
||
margin: 6px 0;
|
||
line-height: 1.4;
|
||
font-size: 14px;
|
||
white-space: pre-wrap;
|
||
word-wrap: break-word;
|
||
overflow-wrap: break-word;
|
||
}
|
||
|
||
.message-time {
|
||
font-size: 10px;
|
||
text-align: right;
|
||
opacity: 0.7;
|
||
margin-top: 4px;
|
||
}
|
||
|
||
.chat-input-container {
|
||
position: sticky;
|
||
bottom: 0;
|
||
background: var(--bg-primary);
|
||
border-top: 1px solid var(--border-color);
|
||
padding: 15px;
|
||
z-index: 10;
|
||
}
|
||
|
||
.chat-input-area {
|
||
display: flex;
|
||
gap: 10px;
|
||
align-items: flex-end;
|
||
max-height: 120px;
|
||
}
|
||
|
||
.message-input {
|
||
flex: 1;
|
||
padding: 12px 15px;
|
||
border: 2px solid var(--border-color);
|
||
border-radius: 25px;
|
||
outline: none;
|
||
font-size: 14px;
|
||
transition: border-color 0.3s ease;
|
||
background: var(--bg-primary);
|
||
color: var(--text-primary);
|
||
resize: none;
|
||
min-height: 44px;
|
||
max-height: 100px;
|
||
font-family: inherit;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.message-input:focus {
|
||
border-color: var(--primary-color);
|
||
}
|
||
|
||
.send-button {
|
||
padding: 12px 20px;
|
||
background: linear-gradient(
|
||
135deg,
|
||
var(--primary-color),
|
||
var(--primary-hover)
|
||
);
|
||
color: white;
|
||
border: none;
|
||
border-radius: 25px;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
transition: all 0.3s ease;
|
||
flex-shrink: 0;
|
||
min-height: 44px;
|
||
}
|
||
|
||
.send-button:hover:not(:disabled) {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 5px 15px rgba(52, 152, 219, 0.4);
|
||
}
|
||
|
||
.send-button:disabled {
|
||
background: var(--bg-tertiary);
|
||
color: var(--text-secondary);
|
||
cursor: not-allowed;
|
||
transform: none;
|
||
box-shadow: none;
|
||
}
|
||
|
||
.system-message {
|
||
text-align: center;
|
||
color: var(--text-secondary);
|
||
font-style: italic;
|
||
margin: 10px 0;
|
||
font-size: 12px;
|
||
padding: 8px;
|
||
background: var(--bg-secondary);
|
||
border-radius: 10px;
|
||
align-self: center;
|
||
max-width: 80%;
|
||
}
|
||
|
||
.empty-state {
|
||
text-align: center;
|
||
color: var(--text-secondary);
|
||
padding: 30px 15px;
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
align-items: center;
|
||
}
|
||
|
||
.empty-state h3 {
|
||
margin-bottom: 8px;
|
||
font-weight: 300;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.empty-state p {
|
||
font-size: 14px;
|
||
}
|
||
|
||
/* Стили для кнопки удаления сообщения */
|
||
.message-actions {
|
||
position: absolute;
|
||
top: 5px;
|
||
right: 8px;
|
||
opacity: 0;
|
||
transition: opacity 0.3s ease;
|
||
}
|
||
|
||
.message:hover .message-actions {
|
||
opacity: 1;
|
||
}
|
||
|
||
.delete-btn {
|
||
background: none;
|
||
border: none;
|
||
color: var(--text-secondary);
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
padding: 4px;
|
||
border-radius: 3px;
|
||
}
|
||
|
||
.delete-btn:hover {
|
||
background: rgba(0, 0, 0, 0.1);
|
||
color: var(--error-color);
|
||
}
|
||
|
||
/* Стили для модального окна профиля */
|
||
.profile-modal-overlay {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(0, 0, 0, 0.5);
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
z-index: 1000;
|
||
}
|
||
|
||
.profile-modal {
|
||
background: var(--bg-primary);
|
||
border-radius: 15px;
|
||
padding: 0;
|
||
max-width: 400px;
|
||
width: 90%;
|
||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
|
||
margin: 20px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.profile-header {
|
||
background: linear-gradient(
|
||
135deg,
|
||
var(--primary-color),
|
||
var(--primary-hover)
|
||
);
|
||
padding: 30px 20px;
|
||
text-align: center;
|
||
color: white;
|
||
position: relative;
|
||
}
|
||
|
||
.profile-avatar-large {
|
||
width: 100px;
|
||
height: 100px;
|
||
border-radius: 50%;
|
||
margin: 0 auto 15px;
|
||
background: rgba(255, 255, 255, 0.2);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: white;
|
||
font-size: 36px;
|
||
font-weight: 600;
|
||
border: 4px solid white;
|
||
position: relative;
|
||
cursor: pointer;
|
||
transition: transform 0.3s ease;
|
||
}
|
||
|
||
.profile-avatar-large:hover {
|
||
transform: scale(1.05);
|
||
}
|
||
|
||
.profile-avatar-large img {
|
||
width: 100%;
|
||
height: 100%;
|
||
border-radius: 50%;
|
||
object-fit: cover;
|
||
}
|
||
|
||
.avatar-upload-overlay {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(0, 0, 0, 0.7);
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
opacity: 0;
|
||
transition: opacity 0.3s ease;
|
||
color: white;
|
||
font-size: 12px;
|
||
text-align: center;
|
||
padding: 10px;
|
||
}
|
||
|
||
.profile-avatar-large:hover .avatar-upload-overlay {
|
||
opacity: 1;
|
||
}
|
||
|
||
.profile-name {
|
||
font-size: 20px;
|
||
font-weight: 600;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.profile-phone {
|
||
font-size: 14px;
|
||
opacity: 0.9;
|
||
}
|
||
|
||
.profile-content {
|
||
padding: 25px;
|
||
}
|
||
|
||
.profile-section {
|
||
margin-bottom: 25px;
|
||
}
|
||
|
||
.profile-section-title {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
margin-bottom: 15px;
|
||
color: var(--text-primary);
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.profile-section-title i {
|
||
font-size: 18px;
|
||
}
|
||
|
||
.profile-info {
|
||
background: var(--bg-secondary);
|
||
border-radius: 10px;
|
||
padding: 15px;
|
||
}
|
||
|
||
.info-item {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 10px 0;
|
||
border-bottom: 1px solid var(--border-color);
|
||
}
|
||
|
||
.info-item:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.info-label {
|
||
font-size: 14px;
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
.info-value {
|
||
font-size: 14px;
|
||
color: var(--text-primary);
|
||
font-weight: 500;
|
||
}
|
||
|
||
.profile-actions {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
}
|
||
|
||
.profile-btn {
|
||
padding: 12px 16px;
|
||
border: none;
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
transition: all 0.3s ease;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
}
|
||
|
||
.profile-btn-primary {
|
||
background: linear-gradient(
|
||
135deg,
|
||
var(--primary-color),
|
||
var(--primary-hover)
|
||
);
|
||
color: white;
|
||
}
|
||
|
||
.profile-btn-primary:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 5px 15px rgba(52, 152, 219, 0.4);
|
||
}
|
||
|
||
.profile-btn-danger {
|
||
background: var(--error-color);
|
||
color: white;
|
||
}
|
||
|
||
.profile-btn-danger:hover {
|
||
background: #c0392b;
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 5px 15px rgba(231, 76, 60, 0.4);
|
||
}
|
||
|
||
.profile-btn-secondary {
|
||
background: var(--bg-tertiary);
|
||
color: var(--text-primary);
|
||
}
|
||
|
||
.profile-btn-secondary:hover {
|
||
background: var(--border-color);
|
||
}
|
||
|
||
/* Стили для скрытого input file */
|
||
.avatar-input {
|
||
display: none;
|
||
}
|
||
|
||
/* Scrollbars */
|
||
::-webkit-scrollbar {
|
||
width: 6px;
|
||
}
|
||
|
||
::-webkit-scrollbar-track {
|
||
background: var(--bg-tertiary);
|
||
border-radius: 3px;
|
||
}
|
||
|
||
::-webkit-scrollbar-thumb {
|
||
background: var(--border-color);
|
||
border-radius: 3px;
|
||
}
|
||
|
||
::-webkit-scrollbar-thumb:hover {
|
||
background: var(--text-secondary);
|
||
}
|
||
|
||
/* Desktop Layout */
|
||
.desktop-layout {
|
||
display: flex;
|
||
height: 100vh;
|
||
width: 100%;
|
||
}
|
||
|
||
/* Connection status */
|
||
.connection-status {
|
||
position: fixed;
|
||
top: 10px;
|
||
right: 10px;
|
||
padding: 8px 12px;
|
||
border-radius: 20px;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
z-index: 1000;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.connection-status.connected {
|
||
background: var(--success-color);
|
||
color: white;
|
||
}
|
||
|
||
.connection-status.disconnected {
|
||
background: var(--error-color);
|
||
color: white;
|
||
}
|
||
|
||
.connection-status.connecting {
|
||
background: var(--warning-color);
|
||
color: white;
|
||
}
|
||
|
||
/* Mobile Styles */
|
||
@media (max-width: 768px) {
|
||
.auth-container {
|
||
max-width: 100%;
|
||
padding: 20px;
|
||
border-radius: 0;
|
||
}
|
||
|
||
.desktop-layout {
|
||
position: relative;
|
||
height: 100%;
|
||
}
|
||
|
||
.chats-panel {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100vh;
|
||
z-index: 100;
|
||
transform: translateX(-100%);
|
||
}
|
||
|
||
.chats-panel.active {
|
||
transform: translateX(0);
|
||
}
|
||
|
||
.chat-panel {
|
||
width: 100%;
|
||
height: 100vh;
|
||
}
|
||
|
||
.mobile-header {
|
||
display: flex;
|
||
}
|
||
|
||
.back-btn {
|
||
display: block;
|
||
}
|
||
|
||
.message {
|
||
max-width: 90%;
|
||
margin: 6px 0;
|
||
}
|
||
|
||
.modal {
|
||
margin: 15px;
|
||
}
|
||
|
||
.modal-buttons {
|
||
flex-direction: column;
|
||
}
|
||
|
||
.modal-btn {
|
||
width: 100%;
|
||
}
|
||
|
||
.chat-input-container {
|
||
padding: 10px;
|
||
padding-bottom: max(10px, env(safe-area-inset-bottom));
|
||
}
|
||
|
||
.chat-input-area {
|
||
gap: 8px;
|
||
}
|
||
|
||
.message-input {
|
||
padding: 10px 12px;
|
||
min-height: 44px;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.send-button {
|
||
padding: 10px 16px;
|
||
min-height: 44px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.connection-status {
|
||
top: 60px;
|
||
right: 10px;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 480px) {
|
||
.auth-container {
|
||
padding: 15px;
|
||
}
|
||
|
||
.auth-tab {
|
||
padding: 12px 8px;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.form-control {
|
||
padding: 10px 12px;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.btn {
|
||
padding: 12px;
|
||
}
|
||
|
||
.chat-header {
|
||
padding: 10px 12px;
|
||
min-height: 60px;
|
||
}
|
||
|
||
.chat-messages {
|
||
padding: 10px;
|
||
padding-bottom: 70px;
|
||
}
|
||
|
||
.message {
|
||
padding: 10px 12px;
|
||
max-width: 95%;
|
||
margin: 4px 0;
|
||
}
|
||
|
||
.message-content {
|
||
font-size: 14px;
|
||
}
|
||
|
||
.chat-input-container {
|
||
padding: 8px;
|
||
padding-bottom: max(8px, env(safe-area-inset-bottom));
|
||
}
|
||
|
||
.message-input {
|
||
padding: 8px 12px;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.send-button {
|
||
padding: 8px 14px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.mobile-title {
|
||
font-size: 16px;
|
||
}
|
||
|
||
.chat-header-username {
|
||
font-size: 15px;
|
||
}
|
||
}
|
||
|
||
/* Very small screens */
|
||
@media (max-width: 320px) {
|
||
.chat-avatar {
|
||
width: 40px;
|
||
height: 40px;
|
||
font-size: 16px;
|
||
margin-right: 8px;
|
||
}
|
||
|
||
.chat-header-avatar {
|
||
width: 35px;
|
||
height: 35px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.chat-item {
|
||
padding: 10px 12px;
|
||
}
|
||
|
||
.chat-input-area {
|
||
gap: 6px;
|
||
}
|
||
|
||
.message-input {
|
||
padding: 8px 10px;
|
||
}
|
||
|
||
.send-button {
|
||
padding: 8px 12px;
|
||
min-width: 60px;
|
||
}
|
||
}
|
||
|
||
/* Safe area support for notches */
|
||
@supports (padding: max(0px)) {
|
||
.chat-input-container {
|
||
padding-bottom: max(15px, env(safe-area-inset-bottom));
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.chat-input-container {
|
||
padding-bottom: max(10px, env(safe-area-inset-bottom));
|
||
}
|
||
}
|
||
|
||
@media (max-width: 480px) {
|
||
.chat-input-container {
|
||
padding-bottom: max(8px, env(safe-area-inset-bottom));
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Prevent body scroll when modal is open */
|
||
body.modal-open {
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* Theme transition */
|
||
* {
|
||
transition:
|
||
background-color 0.3s ease,
|
||
color 0.3s ease,
|
||
border-color 0.3s ease;
|
||
}
|
||
|
||
/* Ensure messages are always fully visible */
|
||
.chat-messages-container {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
min-height: 0;
|
||
position: relative;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<!-- Connection Status -->
|
||
<div
|
||
id="connectionStatus"
|
||
class="connection-status"
|
||
style="display: none"
|
||
></div>
|
||
|
||
<!-- Authentication Container -->
|
||
<div id="authContainer" class="auth-container">
|
||
<div class="auth-tabs">
|
||
<div class="auth-tab active" onclick="showTab('login')">
|
||
Login
|
||
</div>
|
||
<div class="auth-tab" onclick="showTab('register')">
|
||
Register
|
||
</div>
|
||
</div>
|
||
|
||
<div id="alert" class="alert" style="display: none"></div>
|
||
|
||
<!-- Login Form -->
|
||
<div id="loginForm" class="auth-form active">
|
||
<div class="form-group">
|
||
<label for="loginPhone">Phone Number</label>
|
||
<input
|
||
type="text"
|
||
id="loginPhone"
|
||
class="form-control"
|
||
placeholder="+1234567890"
|
||
/>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="loginPassword">Password</label>
|
||
<input
|
||
type="password"
|
||
id="loginPassword"
|
||
class="form-control"
|
||
placeholder="Enter your password"
|
||
/>
|
||
</div>
|
||
<button class="btn" onclick="login()">Login</button>
|
||
</div>
|
||
|
||
<!-- Register Form -->
|
||
<div id="registerForm" class="auth-form">
|
||
<div class="form-group">
|
||
<label for="registerPhone">Phone Number</label>
|
||
<input
|
||
type="text"
|
||
id="registerPhone"
|
||
class="form-control"
|
||
placeholder="+1234567890"
|
||
/>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="registerUsername">Username</label>
|
||
<input
|
||
type="text"
|
||
id="registerUsername"
|
||
class="form-control"
|
||
placeholder="Choose a username"
|
||
/>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="registerPassword">Password</label>
|
||
<input
|
||
type="password"
|
||
id="registerPassword"
|
||
class="form-control"
|
||
placeholder="Choose a password (min 6 characters)"
|
||
/>
|
||
</div>
|
||
<button class="btn" onclick="register()">Register</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Chat Container -->
|
||
<div class="chat-container" id="chatContainer">
|
||
<!-- Mobile Header -->
|
||
<div class="mobile-header">
|
||
<button class="menu-btn" onclick="toggleChatsPanel()">
|
||
☰
|
||
</button>
|
||
<div class="mobile-title" id="mobileChatTitle">Secure Chat</div>
|
||
<button class="profile-header-btn" onclick="openProfileModal()">
|
||
<div class="profile-header-avatar" id="profileHeaderAvatar">
|
||
<!-- Avatar will be populated here -->
|
||
</div>
|
||
</button>
|
||
</div>
|
||
|
||
<div class="desktop-layout">
|
||
<!-- Chats Panel -->
|
||
<div class="chats-panel" id="chatsPanel">
|
||
<div class="chats-header">
|
||
<h2>Chats</h2>
|
||
<button
|
||
class="profile-header-btn"
|
||
onclick="openProfileModal()"
|
||
>
|
||
<div
|
||
class="profile-header-avatar"
|
||
id="profileHeaderAvatarDesktop"
|
||
>
|
||
<!-- Avatar will be populated here -->
|
||
</div>
|
||
</button>
|
||
</div>
|
||
|
||
<div class="chats-tabs">
|
||
<div
|
||
class="chat-tab active"
|
||
onclick="showChatTab('existing')"
|
||
>
|
||
My Chats
|
||
</div>
|
||
<div class="chat-tab" onclick="showChatTab('search')">
|
||
New Chat
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Search Container -->
|
||
<div class="search-container" id="searchContainer">
|
||
<input
|
||
type="text"
|
||
id="searchInput"
|
||
class="search-input"
|
||
placeholder="Search by phone or username..."
|
||
/>
|
||
<div class="search-results" id="searchResults"></div>
|
||
</div>
|
||
|
||
<!-- Existing Chats List -->
|
||
<ul class="chat-list" id="chatsList">
|
||
<!-- Chats will be populated here -->
|
||
</ul>
|
||
</div>
|
||
|
||
<!-- Chat Panel -->
|
||
<div class="chat-panel">
|
||
<div class="chat-header">
|
||
<div class="chat-header-info">
|
||
<button class="back-btn" onclick="showChatsPanel()">
|
||
←
|
||
</button>
|
||
<div
|
||
class="chat-header-avatar"
|
||
id="currentUserAvatar"
|
||
>
|
||
<!-- Avatar will be populated here -->
|
||
</div>
|
||
<div class="chat-header-text">
|
||
<div
|
||
class="chat-header-username"
|
||
id="currentUsername"
|
||
>
|
||
Select a chat
|
||
</div>
|
||
<div
|
||
class="chat-header-phone"
|
||
id="currentUserPhone"
|
||
>
|
||
Choose someone to message
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- Убрана кнопка профиля из заголовка чата -->
|
||
</div>
|
||
|
||
<div class="chat-messages-container">
|
||
<div class="chat-messages" id="chatMessages">
|
||
<div class="empty-state">
|
||
<h3>Welcome to Secure Chat</h3>
|
||
<p>Select a chat to start messaging</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="chat-input-container">
|
||
<div class="chat-input-area">
|
||
<textarea
|
||
id="messageInput"
|
||
class="message-input"
|
||
placeholder="Type a message..."
|
||
disabled
|
||
rows="1"
|
||
></textarea>
|
||
<button
|
||
onclick="sendMessage()"
|
||
class="send-button"
|
||
id="sendButton"
|
||
disabled
|
||
>
|
||
Send
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Profile Modal -->
|
||
<div
|
||
id="profileModal"
|
||
class="profile-modal-overlay"
|
||
style="display: none"
|
||
>
|
||
<div class="profile-modal">
|
||
<div class="profile-header">
|
||
<input
|
||
type="file"
|
||
id="avatarInput"
|
||
class="avatar-input"
|
||
accept="image/*"
|
||
/>
|
||
<div
|
||
class="profile-avatar-large"
|
||
id="profileModalAvatar"
|
||
onclick="document.getElementById('avatarInput').click()"
|
||
>
|
||
<!-- Large avatar will be populated here -->
|
||
<div class="avatar-upload-overlay">Change Photo</div>
|
||
</div>
|
||
<div class="profile-name" id="profileModalName">User</div>
|
||
<div class="profile-phone" id="profileModalPhone">
|
||
+1234567890
|
||
</div>
|
||
</div>
|
||
|
||
<div class="profile-content">
|
||
<div class="profile-section">
|
||
<div class="profile-section-title">
|
||
📱 Account Information
|
||
</div>
|
||
<div class="profile-info">
|
||
<div class="info-item">
|
||
<div class="info-label">Username</div>
|
||
<div
|
||
class="info-value"
|
||
id="profileModalUsername"
|
||
>
|
||
user123
|
||
</div>
|
||
</div>
|
||
<div class="info-item">
|
||
<div class="info-label">Phone Number</div>
|
||
<div
|
||
class="info-value"
|
||
id="profileModalPhoneNumber"
|
||
>
|
||
+1234567890
|
||
</div>
|
||
</div>
|
||
<div class="info-item">
|
||
<div class="info-label">Status</div>
|
||
<div class="info-value">Online</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="profile-section">
|
||
<div class="profile-section-title">⚙️ Settings</div>
|
||
<div class="profile-actions">
|
||
<button
|
||
class="profile-btn profile-btn-secondary"
|
||
onclick="toggleTheme()"
|
||
>
|
||
🌓 Switch Theme
|
||
</button>
|
||
<button
|
||
class="profile-btn profile-btn-primary"
|
||
onclick="changePhoneNumber()"
|
||
>
|
||
📞 Change Phone Number
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="profile-section">
|
||
<div class="profile-actions">
|
||
<button
|
||
class="profile-btn profile-btn-danger"
|
||
onclick="logout()"
|
||
>
|
||
🚪 Logout
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Modal for delete confirmation -->
|
||
<div id="deleteModal" class="modal-overlay" style="display: none">
|
||
<div class="modal">
|
||
<h3>Delete Message</h3>
|
||
<p>How would you like to delete this message?</p>
|
||
<div class="modal-buttons">
|
||
<button
|
||
class="modal-btn modal-btn-cancel"
|
||
onclick="closeDeleteModal()"
|
||
>
|
||
Cancel
|
||
</button>
|
||
<button
|
||
class="modal-btn modal-btn-delete-for-me"
|
||
onclick="deleteMessageForMe()"
|
||
>
|
||
Delete for me
|
||
</button>
|
||
<button
|
||
class="modal-btn modal-btn-delete"
|
||
onclick="deleteMessageForEveryone()"
|
||
>
|
||
Delete for everyone
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
let ws = null;
|
||
let currentUser = "";
|
||
let currentToken = "";
|
||
let currentRecipient = "";
|
||
let currentUserAvatar = "";
|
||
let currentUserPhone = "";
|
||
let chats = [];
|
||
let searchResults = [];
|
||
let messageHistory = {};
|
||
let currentChatTab = "existing";
|
||
let messageToDelete = null;
|
||
let isMobile = window.innerWidth <= 768;
|
||
let currentTheme = localStorage.getItem("theme") || "light";
|
||
let reconnectTimeout = null;
|
||
let reconnectAttempts = 0;
|
||
const MAX_RECONNECT_ATTEMPTS = 5;
|
||
const RECONNECT_DELAY = 3000;
|
||
|
||
// Initialize theme
|
||
document.body.setAttribute("data-theme", currentTheme);
|
||
|
||
// Check for saved session on page load
|
||
window.addEventListener("DOMContentLoaded", function () {
|
||
const savedToken = localStorage.getItem("chatToken");
|
||
const savedUsername = localStorage.getItem("chatUsername");
|
||
const savedPhone = localStorage.getItem("chatUserPhone");
|
||
|
||
if (savedToken && savedUsername) {
|
||
currentUserPhone = savedPhone;
|
||
validateToken(savedToken, savedUsername);
|
||
}
|
||
|
||
// Auto-resize textarea
|
||
const messageInput = document.getElementById("messageInput");
|
||
messageInput.addEventListener("input", function () {
|
||
this.style.height = "auto";
|
||
this.style.height = Math.min(this.scrollHeight, 100) + "px";
|
||
});
|
||
|
||
// Update mobile detection on resize
|
||
window.addEventListener("resize", function () {
|
||
isMobile = window.innerWidth <= 768;
|
||
if (!isMobile) {
|
||
// On desktop, ensure chats panel is visible
|
||
document
|
||
.getElementById("chatsPanel")
|
||
.classList.remove("active");
|
||
}
|
||
});
|
||
});
|
||
|
||
function toggleTheme() {
|
||
currentTheme = currentTheme === "light" ? "dark" : "light";
|
||
document.body.setAttribute("data-theme", currentTheme);
|
||
localStorage.setItem("theme", currentTheme);
|
||
}
|
||
|
||
function updateConnectionStatus(status, message = "") {
|
||
const statusElement =
|
||
document.getElementById("connectionStatus");
|
||
statusElement.textContent = message || status;
|
||
statusElement.className = `connection-status ${status}`;
|
||
statusElement.style.display = "block";
|
||
|
||
if (status === "connected") {
|
||
setTimeout(() => {
|
||
statusElement.style.display = "none";
|
||
}, 3000);
|
||
}
|
||
}
|
||
|
||
function openProfileModal() {
|
||
updateProfileModal();
|
||
document.getElementById("profileModal").style.display = "flex";
|
||
document.body.classList.add("modal-open");
|
||
}
|
||
|
||
function closeProfileModal() {
|
||
document.getElementById("profileModal").style.display = "none";
|
||
document.body.classList.remove("modal-open");
|
||
}
|
||
|
||
function updateProfileModal() {
|
||
// Update profile modal content
|
||
document.getElementById("profileModalName").textContent =
|
||
currentUser;
|
||
document.getElementById("profileModalUsername").textContent =
|
||
currentUser;
|
||
document.getElementById("profileModalPhone").textContent =
|
||
currentUserPhone;
|
||
document.getElementById("profileModalPhoneNumber").textContent =
|
||
currentUserPhone;
|
||
|
||
// Update large avatar in modal
|
||
const modalAvatar =
|
||
document.getElementById("profileModalAvatar");
|
||
if (currentUserAvatar) {
|
||
modalAvatar.innerHTML = `<img src="${currentUserAvatar}" alt="${currentUser}">`;
|
||
} else {
|
||
modalAvatar.innerHTML = currentUser.charAt(0).toUpperCase();
|
||
}
|
||
|
||
// Add the upload overlay
|
||
modalAvatar.innerHTML +=
|
||
'<div class="avatar-upload-overlay">Change Photo</div>';
|
||
}
|
||
|
||
function updateAllAvatars() {
|
||
const avatarContent = currentUserAvatar
|
||
? `<img src="${currentUserAvatar}" alt="${currentUser}">`
|
||
: currentUser.charAt(0).toUpperCase();
|
||
|
||
// Update all avatar locations (убран profileHeaderAvatarMain)
|
||
const avatarLocations = [
|
||
"profileHeaderAvatar",
|
||
"profileHeaderAvatarDesktop",
|
||
"currentUserAvatar",
|
||
];
|
||
|
||
avatarLocations.forEach((id) => {
|
||
const element = document.getElementById(id);
|
||
if (element) {
|
||
element.innerHTML = avatarContent;
|
||
}
|
||
});
|
||
|
||
// Update profile modal avatar
|
||
updateProfileModal();
|
||
}
|
||
|
||
function changePhoneNumber() {
|
||
const newPhone = prompt(
|
||
"Enter your new phone number:",
|
||
currentUserPhone,
|
||
);
|
||
if (newPhone && newPhone.trim() !== "") {
|
||
// In a real app, you would send this to the server
|
||
currentUserPhone = newPhone.trim();
|
||
localStorage.setItem("chatUserPhone", currentUserPhone);
|
||
updateProfileModal();
|
||
showAlert("Phone number updated successfully!", "success");
|
||
}
|
||
}
|
||
|
||
function showTab(tabName) {
|
||
document
|
||
.querySelectorAll(".auth-tab")
|
||
.forEach((tab) => tab.classList.remove("active"));
|
||
document
|
||
.querySelectorAll(".auth-form")
|
||
.forEach((form) => form.classList.remove("active"));
|
||
|
||
if (tabName === "login") {
|
||
document
|
||
.querySelector(".auth-tab:nth-child(1)")
|
||
.classList.add("active");
|
||
document
|
||
.getElementById("loginForm")
|
||
.classList.add("active");
|
||
} else {
|
||
document
|
||
.querySelector(".auth-tab:nth-child(2)")
|
||
.classList.add("active");
|
||
document
|
||
.getElementById("registerForm")
|
||
.classList.add("active");
|
||
}
|
||
|
||
hideAlert();
|
||
}
|
||
|
||
function showAlert(message, type) {
|
||
const alert = document.getElementById("alert");
|
||
alert.textContent = message;
|
||
alert.className = `alert alert-${type}`;
|
||
alert.style.display = "block";
|
||
|
||
// Auto hide after 5 seconds
|
||
setTimeout(hideAlert, 5000);
|
||
}
|
||
|
||
function hideAlert() {
|
||
document.getElementById("alert").style.display = "none";
|
||
}
|
||
|
||
// Mobile navigation functions
|
||
function toggleChatsPanel() {
|
||
const chatsPanel = document.getElementById("chatsPanel");
|
||
chatsPanel.classList.toggle("active");
|
||
}
|
||
|
||
function showChatsPanel() {
|
||
const chatsPanel = document.getElementById("chatsPanel");
|
||
chatsPanel.classList.add("active");
|
||
}
|
||
|
||
function hideChatsPanel() {
|
||
const chatsPanel = document.getElementById("chatsPanel");
|
||
chatsPanel.classList.remove("active");
|
||
}
|
||
|
||
function reconnectWebSocket() {
|
||
if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
|
||
reconnectAttempts++;
|
||
updateConnectionStatus(
|
||
"connecting",
|
||
`Reconnecting... (${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})`,
|
||
);
|
||
|
||
reconnectTimeout = setTimeout(() => {
|
||
console.log(
|
||
`Attempting to reconnect... (${reconnectAttempts})`,
|
||
);
|
||
connectWebSocket();
|
||
}, RECONNECT_DELAY);
|
||
} else {
|
||
updateConnectionStatus(
|
||
"disconnected",
|
||
"Failed to connect. Please refresh the page.",
|
||
);
|
||
showAlert(
|
||
"Connection lost. Please refresh the page.",
|
||
"error",
|
||
);
|
||
}
|
||
}
|
||
|
||
function clearReconnection() {
|
||
if (reconnectTimeout) {
|
||
clearTimeout(reconnectTimeout);
|
||
reconnectTimeout = null;
|
||
}
|
||
reconnectAttempts = 0;
|
||
}
|
||
|
||
async function validateToken(token, username) {
|
||
try {
|
||
const response = await fetch("/api/validate-token", {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({ token }),
|
||
});
|
||
|
||
const data = await response.json();
|
||
|
||
if (data.success) {
|
||
currentUser = data.username;
|
||
currentToken = token;
|
||
currentUserAvatar = data.avatar_url;
|
||
|
||
localStorage.setItem("chatToken", token);
|
||
localStorage.setItem("chatUsername", data.username);
|
||
|
||
document.getElementById("authContainer").style.display =
|
||
"none";
|
||
document.getElementById("chatContainer").style.display =
|
||
"flex";
|
||
|
||
updateUserProfile();
|
||
loadChats();
|
||
connectWebSocket();
|
||
} else {
|
||
localStorage.removeItem("chatToken");
|
||
localStorage.removeItem("chatUsername");
|
||
showAlert(
|
||
"Session expired. Please login again.",
|
||
"error",
|
||
);
|
||
}
|
||
} catch (error) {
|
||
console.error("Token validation failed:", error);
|
||
localStorage.removeItem("chatToken");
|
||
localStorage.removeItem("chatUsername");
|
||
showAlert(
|
||
"Session validation failed. Please login again.",
|
||
"error",
|
||
);
|
||
}
|
||
}
|
||
|
||
function updateUserProfile() {
|
||
document.getElementById("currentUsername").textContent =
|
||
currentUser;
|
||
updateAllAvatars();
|
||
}
|
||
|
||
async function loadChats() {
|
||
try {
|
||
console.log("Loading chats for user:", currentUser);
|
||
const response = await fetch("/api/chats", {
|
||
headers: { Authorization: `Bearer ${currentToken}` },
|
||
});
|
||
|
||
console.log("Chats response status:", response.status);
|
||
if (response.ok) {
|
||
chats = await response.json();
|
||
console.log("Loaded chats:", chats);
|
||
renderChatsList();
|
||
} else {
|
||
console.error(
|
||
"Failed to load chats, status:",
|
||
response.status,
|
||
);
|
||
}
|
||
} catch (error) {
|
||
console.error("Error loading chats:", error);
|
||
}
|
||
}
|
||
|
||
// Function to mark messages as read
|
||
async function markMessagesAsRead(fromUser) {
|
||
try {
|
||
const response = await fetch("/api/mark-read", {
|
||
method: "POST",
|
||
headers: {
|
||
"Content-Type": "application/json",
|
||
Authorization: `Bearer ${currentToken}`,
|
||
},
|
||
body: JSON.stringify({
|
||
from_user: fromUser,
|
||
to_user: currentUser,
|
||
}),
|
||
});
|
||
|
||
const data = await response.json();
|
||
if (data.success) {
|
||
console.log(
|
||
`✅ Marked messages as read from ${fromUser}`,
|
||
);
|
||
// Обновляем список чатов чтобы убрать красный badge
|
||
loadChats();
|
||
} else {
|
||
console.error(
|
||
"❌ Failed to mark messages as read:",
|
||
data.error,
|
||
);
|
||
}
|
||
} catch (error) {
|
||
console.error("❌ Error marking messages as read:", error);
|
||
}
|
||
}
|
||
|
||
// Function to delete message
|
||
async function deleteMessage(messageId, forEveryone) {
|
||
try {
|
||
const response = await fetch("/api/delete-message", {
|
||
method: "POST",
|
||
headers: {
|
||
"Content-Type": "application/json",
|
||
Authorization: `Bearer ${currentToken}`,
|
||
},
|
||
body: JSON.stringify({
|
||
message_id: messageId,
|
||
for_everyone: forEveryone,
|
||
}),
|
||
});
|
||
|
||
const data = await response.json();
|
||
if (data.success) {
|
||
console.log(`✅ Message deleted: ${data.message}`);
|
||
showAlert(data.message, "success");
|
||
|
||
// Remove message from UI
|
||
const messageElement = document.querySelector(
|
||
`[data-message-id="${messageId}"]`,
|
||
);
|
||
if (messageElement) {
|
||
messageElement.remove();
|
||
}
|
||
|
||
// Update chat history
|
||
if (messageHistory[currentRecipient]) {
|
||
messageHistory[currentRecipient] = messageHistory[
|
||
currentRecipient
|
||
].filter((msg) => msg.id !== messageId);
|
||
}
|
||
|
||
// Reload chats to update last message
|
||
loadChats();
|
||
} else {
|
||
console.error(
|
||
"❌ Failed to delete message:",
|
||
data.message,
|
||
);
|
||
showAlert(data.message, "error");
|
||
}
|
||
} catch (error) {
|
||
console.error("❌ Error deleting message:", error);
|
||
showAlert("Error deleting message", "error");
|
||
}
|
||
}
|
||
|
||
// Function to upload avatar
|
||
async function uploadAvatar(file) {
|
||
const formData = new FormData();
|
||
formData.append("avatar", file);
|
||
|
||
try {
|
||
const response = await fetch("/api/upload-avatar", {
|
||
method: "POST",
|
||
headers: {
|
||
Authorization: `Bearer ${currentToken}`,
|
||
},
|
||
body: formData,
|
||
});
|
||
|
||
const data = await response.json();
|
||
if (data.success) {
|
||
currentUserAvatar = data.avatar_url;
|
||
updateAllAvatars();
|
||
showAlert("Avatar uploaded successfully!", "success");
|
||
loadChats(); // Refresh chats to show updated avatar
|
||
} else {
|
||
showAlert(data.message, "error");
|
||
}
|
||
} catch (error) {
|
||
console.error("Error uploading avatar:", error);
|
||
showAlert("Error uploading avatar", "error");
|
||
}
|
||
}
|
||
|
||
// Modal functions
|
||
function showDeleteModal(messageId) {
|
||
messageToDelete = messageId;
|
||
document.getElementById("deleteModal").style.display = "flex";
|
||
document.body.classList.add("modal-open");
|
||
}
|
||
|
||
function closeDeleteModal() {
|
||
messageToDelete = null;
|
||
document.getElementById("deleteModal").style.display = "none";
|
||
document.body.classList.remove("modal-open");
|
||
}
|
||
|
||
function deleteMessageForMe() {
|
||
if (messageToDelete) {
|
||
deleteMessage(messageToDelete, false);
|
||
closeDeleteModal();
|
||
}
|
||
}
|
||
|
||
function deleteMessageForEveryone() {
|
||
if (messageToDelete) {
|
||
deleteMessage(messageToDelete, true);
|
||
closeDeleteModal();
|
||
}
|
||
}
|
||
|
||
function renderChatsList() {
|
||
const chatsList = document.getElementById("chatsList");
|
||
chatsList.innerHTML = "";
|
||
|
||
console.log("Rendering chats:", chats);
|
||
|
||
if (chats.length === 0) {
|
||
const emptyState = document.createElement("div");
|
||
emptyState.className = "empty-state";
|
||
emptyState.innerHTML = `
|
||
<h3>No chats yet</h3>
|
||
<p>Start a new conversation by clicking "New Chat"</p>
|
||
`;
|
||
chatsList.appendChild(emptyState);
|
||
return;
|
||
}
|
||
|
||
chats.forEach((chat) => {
|
||
const li = document.createElement("li");
|
||
li.className = "chat-item";
|
||
if (chat.username === currentRecipient) {
|
||
li.classList.add("active");
|
||
}
|
||
|
||
const lastMessageTime = chat.last_message_time
|
||
? formatTime(chat.last_message_time)
|
||
: "";
|
||
const lastMessage = chat.last_message || "No messages yet";
|
||
|
||
const avatarContent = chat.avatar_url
|
||
? `<img src="${chat.avatar_url}" alt="${chat.username}">`
|
||
: chat.username.charAt(0).toUpperCase();
|
||
|
||
li.innerHTML = `
|
||
<div class="chat-avatar">${avatarContent}</div>
|
||
<div class="chat-info">
|
||
<div class="chat-username">${chat.username}</div>
|
||
<div class="chat-phone">${chat.phone}</div>
|
||
<div class="chat-last-message">${lastMessage}</div>
|
||
</div>
|
||
<div class="chat-meta">
|
||
<div class="chat-time">${lastMessageTime}</div>
|
||
${chat.unread_count > 0 ? `<div class="unread-badge">${chat.unread_count}</div>` : ""}
|
||
</div>
|
||
`;
|
||
|
||
li.onclick = () => {
|
||
selectChat(chat.username, chat.phone, chat.avatar_url);
|
||
if (isMobile) {
|
||
hideChatsPanel();
|
||
}
|
||
};
|
||
chatsList.appendChild(li);
|
||
});
|
||
}
|
||
|
||
function showChatTab(tab) {
|
||
currentChatTab = tab;
|
||
document
|
||
.querySelectorAll(".chat-tab")
|
||
.forEach((tab) => tab.classList.remove("active"));
|
||
|
||
if (tab === "existing") {
|
||
document
|
||
.querySelector(".chat-tab:nth-child(1)")
|
||
.classList.add("active");
|
||
document.getElementById("searchContainer").style.display =
|
||
"none";
|
||
renderChatsList();
|
||
} else {
|
||
document
|
||
.querySelector(".chat-tab:nth-child(2)")
|
||
.classList.add("active");
|
||
document.getElementById("searchContainer").style.display =
|
||
"block";
|
||
document.getElementById("searchResults").innerHTML =
|
||
'<div class="empty-state"><p>Search for users by phone or username</p></div>';
|
||
document.getElementById("searchInput").value = "";
|
||
}
|
||
}
|
||
|
||
let searchTimeout;
|
||
document
|
||
.getElementById("searchInput")
|
||
.addEventListener("input", function (e) {
|
||
clearTimeout(searchTimeout);
|
||
const query = e.target.value.trim();
|
||
|
||
if (query.length < 2) {
|
||
document.getElementById("searchResults").innerHTML =
|
||
'<div class="empty-state"><p>Enter at least 2 characters to search</p></div>';
|
||
return;
|
||
}
|
||
|
||
searchTimeout = setTimeout(() => searchUsers(query), 300);
|
||
});
|
||
|
||
async function searchUsers(query) {
|
||
try {
|
||
const response = await fetch(
|
||
`/api/search-users?q=${encodeURIComponent(query)}`,
|
||
{
|
||
headers: {
|
||
Authorization: `Bearer ${currentToken}`,
|
||
},
|
||
},
|
||
);
|
||
|
||
if (response.ok) {
|
||
searchResults = await response.json();
|
||
renderSearchResults();
|
||
} else {
|
||
console.error("Search failed");
|
||
}
|
||
} catch (error) {
|
||
console.error("Error searching users:", error);
|
||
}
|
||
}
|
||
|
||
function renderSearchResults() {
|
||
const searchResultsContainer =
|
||
document.getElementById("searchResults");
|
||
searchResultsContainer.innerHTML = "";
|
||
|
||
if (searchResults.length === 0) {
|
||
searchResultsContainer.innerHTML =
|
||
'<div class="empty-state"><p>No users found</p></div>';
|
||
return;
|
||
}
|
||
|
||
searchResults.forEach((user) => {
|
||
const div = document.createElement("div");
|
||
div.className = "chat-item";
|
||
|
||
const avatarContent = user.avatar_url
|
||
? `<img src="${user.avatar_url}" alt="${user.username}">`
|
||
: user.username.charAt(0).toUpperCase();
|
||
|
||
div.innerHTML = `
|
||
<div class="chat-avatar">${avatarContent}</div>
|
||
<div class="chat-info">
|
||
<div class="chat-username">${user.username}</div>
|
||
<div class="chat-phone">${user.phone}</div>
|
||
<div class="chat-last-message">Click to start chatting</div>
|
||
</div>
|
||
`;
|
||
|
||
div.onclick = () => {
|
||
selectChat(user.username, user.phone, user.avatar_url);
|
||
showChatTab("existing");
|
||
document.getElementById("searchInput").value = "";
|
||
if (isMobile) {
|
||
hideChatsPanel();
|
||
}
|
||
};
|
||
|
||
searchResultsContainer.appendChild(div);
|
||
});
|
||
}
|
||
|
||
async function register() {
|
||
const phone = document
|
||
.getElementById("registerPhone")
|
||
.value.trim();
|
||
const username = document
|
||
.getElementById("registerUsername")
|
||
.value.trim();
|
||
const password =
|
||
document.getElementById("registerPassword").value;
|
||
|
||
if (!phone || !username || !password) {
|
||
showAlert("Please fill all fields", "error");
|
||
return;
|
||
}
|
||
|
||
if (password.length < 6) {
|
||
showAlert(
|
||
"Password must be at least 6 characters",
|
||
"error",
|
||
);
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const response = await fetch("/api/register", {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({ phone, username, password }),
|
||
});
|
||
|
||
const data = await response.json();
|
||
|
||
if (data.success) {
|
||
localStorage.setItem("chatToken", data.token);
|
||
localStorage.setItem("chatUsername", data.username);
|
||
localStorage.setItem("chatUserPhone", phone);
|
||
|
||
currentUser = data.username;
|
||
currentToken = data.token;
|
||
currentUserPhone = phone;
|
||
|
||
document.getElementById("authContainer").style.display =
|
||
"none";
|
||
document.getElementById("chatContainer").style.display =
|
||
"flex";
|
||
|
||
updateUserProfile();
|
||
loadChats();
|
||
connectWebSocket();
|
||
|
||
showAlert("Registration successful!", "success");
|
||
} else {
|
||
showAlert(data.message, "error");
|
||
}
|
||
} catch (error) {
|
||
showAlert(
|
||
"Registration failed. Please try again.",
|
||
"error",
|
||
);
|
||
}
|
||
}
|
||
|
||
async function login() {
|
||
const phone = document
|
||
.getElementById("loginPhone")
|
||
.value.trim();
|
||
const password = document.getElementById("loginPassword").value;
|
||
|
||
if (!phone || !password) {
|
||
showAlert("Please fill all fields", "error");
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const response = await fetch("/api/login", {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({ phone, password }),
|
||
});
|
||
|
||
const data = await response.json();
|
||
|
||
if (data.success) {
|
||
localStorage.setItem("chatToken", data.token);
|
||
localStorage.setItem("chatUsername", data.username);
|
||
localStorage.setItem("chatUserPhone", phone);
|
||
|
||
currentUser = data.username;
|
||
currentToken = data.token;
|
||
currentUserPhone = phone;
|
||
|
||
// Get user profile to load avatar
|
||
const profileResponse = await fetch("/api/profile", {
|
||
headers: {
|
||
Authorization: `Bearer ${currentToken}`,
|
||
},
|
||
});
|
||
|
||
if (profileResponse.ok) {
|
||
const profile = await profileResponse.json();
|
||
currentUserAvatar = profile.avatar_url;
|
||
}
|
||
|
||
document.getElementById("authContainer").style.display =
|
||
"none";
|
||
document.getElementById("chatContainer").style.display =
|
||
"flex";
|
||
|
||
updateUserProfile();
|
||
loadChats();
|
||
connectWebSocket();
|
||
} else {
|
||
showAlert(data.message, "error");
|
||
}
|
||
} catch (error) {
|
||
showAlert("Login failed. Please try again.", "error");
|
||
}
|
||
}
|
||
|
||
function logout() {
|
||
localStorage.removeItem("chatToken");
|
||
localStorage.removeItem("chatUsername");
|
||
localStorage.removeItem("chatUserPhone");
|
||
|
||
if (ws) {
|
||
ws.close();
|
||
ws = null;
|
||
}
|
||
|
||
clearReconnection();
|
||
|
||
currentUser = "";
|
||
currentToken = "";
|
||
currentRecipient = "";
|
||
currentUserAvatar = "";
|
||
currentUserPhone = "";
|
||
chats = [];
|
||
searchResults = [];
|
||
messageHistory = {};
|
||
|
||
document.getElementById("authContainer").style.display =
|
||
"block";
|
||
document.getElementById("chatContainer").style.display = "none";
|
||
|
||
document.getElementById("loginPhone").value = "";
|
||
document.getElementById("loginPassword").value = "";
|
||
document.getElementById("registerPhone").value = "";
|
||
document.getElementById("registerUsername").value = "";
|
||
document.getElementById("registerPassword").value = "";
|
||
|
||
showTab("login");
|
||
}
|
||
|
||
function connectWebSocket() {
|
||
// Clear any existing reconnection attempts
|
||
clearReconnection();
|
||
|
||
const protocol =
|
||
window.location.protocol === "https:" ? "wss:" : "ws:";
|
||
const wsUrl = `${protocol}//${window.location.host}/ws`;
|
||
|
||
updateConnectionStatus("connecting", "Connecting...");
|
||
|
||
ws = new WebSocket(wsUrl);
|
||
|
||
ws.onopen = () => {
|
||
console.log("WebSocket connected");
|
||
updateConnectionStatus("connected", "Connected");
|
||
clearReconnection();
|
||
|
||
ws.send(
|
||
JSON.stringify({
|
||
type: "auth",
|
||
token: currentToken,
|
||
}),
|
||
);
|
||
addSystemMessage("Connected to chat server");
|
||
};
|
||
|
||
ws.onmessage = (event) => {
|
||
console.log("Received message:", event.data);
|
||
try {
|
||
const data = JSON.parse(event.data);
|
||
|
||
if (data.type === "error") {
|
||
showAlert(data.content, "error");
|
||
if (
|
||
data.content.includes("Invalid") ||
|
||
data.content.includes("expired") ||
|
||
data.content.includes("auth")
|
||
) {
|
||
logout();
|
||
}
|
||
} else if (data.type === "system") {
|
||
addSystemMessage(data.content);
|
||
} else if (data.type === "message_deleted") {
|
||
// Handle message deletion from server
|
||
const messageElement = document.querySelector(
|
||
`[data-message-id="${data.message_id}"]`,
|
||
);
|
||
if (messageElement) {
|
||
messageElement.remove();
|
||
}
|
||
|
||
// Update chat history
|
||
if (messageHistory[currentRecipient]) {
|
||
messageHistory[currentRecipient] =
|
||
messageHistory[currentRecipient].filter(
|
||
(msg) => msg.id !== data.message_id,
|
||
);
|
||
}
|
||
|
||
// Reload chats to update last message
|
||
loadChats();
|
||
} else {
|
||
handleChatMessage(data);
|
||
}
|
||
} catch (e) {
|
||
console.error(
|
||
"Error parsing message:",
|
||
e,
|
||
"Raw data:",
|
||
event.data,
|
||
);
|
||
}
|
||
};
|
||
|
||
ws.onclose = (event) => {
|
||
console.log(
|
||
"WebSocket disconnected:",
|
||
event.code,
|
||
event.reason,
|
||
);
|
||
updateConnectionStatus("disconnected", "Disconnected");
|
||
addSystemMessage("Disconnected from server");
|
||
document.getElementById("sendButton").disabled = true;
|
||
document.getElementById("messageInput").disabled = true;
|
||
|
||
// Attempt to reconnect if this wasn't a normal closure
|
||
if (event.code !== 1000 && event.code !== 1001) {
|
||
reconnectWebSocket();
|
||
}
|
||
};
|
||
|
||
ws.onerror = (error) => {
|
||
console.error("WebSocket error:", error);
|
||
updateConnectionStatus("disconnected", "Connection error");
|
||
addSystemMessage("Connection error");
|
||
};
|
||
}
|
||
|
||
async function selectChat(username, phone, avatarUrl) {
|
||
if (!username) return;
|
||
|
||
currentRecipient = username;
|
||
|
||
// Update mobile header
|
||
document.getElementById("mobileChatTitle").textContent =
|
||
username;
|
||
|
||
// Update chat header with recipient info
|
||
const chatHeaderInfo =
|
||
document.querySelector(".chat-header-info");
|
||
const avatarContent = avatarUrl
|
||
? `<img src="${avatarUrl}" alt="${username}">`
|
||
: username.charAt(0).toUpperCase();
|
||
|
||
chatHeaderInfo.innerHTML = `
|
||
<button class="back-btn" onclick="showChatsPanel()">←</button>
|
||
<div class="chat-header-avatar">${avatarContent}</div>
|
||
<div class="chat-header-text">
|
||
<div class="chat-header-username">${username}</div>
|
||
<div class="chat-header-phone">${phone}</div>
|
||
</div>
|
||
`;
|
||
|
||
// Помечаем сообщения как прочитанные при открытии чата
|
||
await markMessagesAsRead(username);
|
||
|
||
// Update active chat in list
|
||
document.querySelectorAll(".chat-item").forEach((item) => {
|
||
item.classList.remove("active");
|
||
const usernameElement =
|
||
item.querySelector(".chat-username");
|
||
if (
|
||
usernameElement &&
|
||
usernameElement.textContent === username
|
||
) {
|
||
item.classList.add("active");
|
||
}
|
||
});
|
||
|
||
// Enable message input
|
||
document.getElementById("sendButton").disabled = false;
|
||
document.getElementById("messageInput").disabled = false;
|
||
|
||
// Focus on input after a short delay to ensure UI is updated
|
||
setTimeout(() => {
|
||
document.getElementById("messageInput").focus();
|
||
}, 100);
|
||
|
||
displayChatHistory(username);
|
||
}
|
||
|
||
function displayChatHistory(username) {
|
||
const chatMessages = document.getElementById("chatMessages");
|
||
chatMessages.innerHTML = "";
|
||
|
||
if (
|
||
messageHistory[username] &&
|
||
messageHistory[username].length > 0
|
||
) {
|
||
messageHistory[username].forEach((msg) =>
|
||
displayMessage(msg, false),
|
||
);
|
||
// Scroll to bottom after rendering
|
||
setTimeout(() => {
|
||
chatMessages.scrollTop = chatMessages.scrollHeight;
|
||
}, 50);
|
||
} else {
|
||
chatMessages.innerHTML = `
|
||
<div class="empty-state">
|
||
<h3>Start a conversation</h3>
|
||
<p>Send a message to start chatting with ${username}</p>
|
||
</div>
|
||
`;
|
||
}
|
||
}
|
||
|
||
function handleChatMessage(msg) {
|
||
console.log("Handling chat message:", msg);
|
||
const otherUser =
|
||
msg.from_user === currentUser ? msg.to_user : msg.from_user;
|
||
|
||
if (!messageHistory[otherUser]) {
|
||
messageHistory[otherUser] = [];
|
||
}
|
||
|
||
const messageExists = messageHistory[otherUser].some(
|
||
(existingMsg) => existingMsg.id === msg.id,
|
||
);
|
||
|
||
if (!messageExists) {
|
||
messageHistory[otherUser].push(msg);
|
||
}
|
||
|
||
loadChats();
|
||
|
||
if (otherUser === currentRecipient) {
|
||
displayMessage(msg, true);
|
||
// Если сообщение пришло в открытый чат, сразу помечаем как прочитанное
|
||
if (msg.from_user === currentRecipient) {
|
||
markMessagesAsRead(currentRecipient);
|
||
}
|
||
}
|
||
}
|
||
|
||
function displayMessage(msg, scrollToBottom) {
|
||
const chatMessages = document.getElementById("chatMessages");
|
||
|
||
// Remove empty state if it exists
|
||
const emptyState = chatMessages.querySelector(".empty-state");
|
||
if (emptyState) {
|
||
emptyState.remove();
|
||
}
|
||
|
||
const messageDiv = document.createElement("div");
|
||
messageDiv.className = `message ${msg.from_user === currentUser ? "outgoing" : "incoming"}`;
|
||
messageDiv.setAttribute("data-message-id", msg.id);
|
||
|
||
if (msg.message_type === "system") {
|
||
messageDiv.className = "system-message";
|
||
messageDiv.textContent = msg.content;
|
||
} else {
|
||
const isOutgoing = msg.from_user === currentUser;
|
||
|
||
if (!isOutgoing) {
|
||
const senderDiv = document.createElement("div");
|
||
senderDiv.className = "message-sender";
|
||
senderDiv.textContent = msg.from_user;
|
||
messageDiv.appendChild(senderDiv);
|
||
}
|
||
|
||
const contentDiv = document.createElement("div");
|
||
contentDiv.className = "message-content";
|
||
contentDiv.textContent = msg.content;
|
||
messageDiv.appendChild(contentDiv);
|
||
|
||
const timeDiv = document.createElement("div");
|
||
timeDiv.className = "message-time";
|
||
const date = new Date(msg.timestamp);
|
||
timeDiv.textContent = date.toLocaleTimeString([], {
|
||
hour: "2-digit",
|
||
minute: "2-digit",
|
||
});
|
||
messageDiv.appendChild(timeDiv);
|
||
|
||
// Add delete button for user's own messages or all messages
|
||
if (msg.from_user === currentUser) {
|
||
const actionsDiv = document.createElement("div");
|
||
actionsDiv.className = "message-actions";
|
||
const deleteBtn = document.createElement("button");
|
||
deleteBtn.className = "delete-btn";
|
||
deleteBtn.innerHTML = "×";
|
||
deleteBtn.title = "Delete message";
|
||
deleteBtn.onclick = (e) => {
|
||
e.stopPropagation();
|
||
showDeleteModal(msg.id);
|
||
};
|
||
actionsDiv.appendChild(deleteBtn);
|
||
messageDiv.appendChild(actionsDiv);
|
||
}
|
||
}
|
||
|
||
chatMessages.appendChild(messageDiv);
|
||
console.log("message added");
|
||
|
||
if (scrollToBottom) {
|
||
setTimeout(() => {
|
||
chatMessages.scrollTop = chatMessages.scrollHeight;
|
||
}, 100);
|
||
}
|
||
}
|
||
|
||
function addSystemMessage(content) {
|
||
const chatMessages = document.getElementById("chatMessages");
|
||
|
||
// Remove empty state if it exists
|
||
const emptyState = chatMessages.querySelector(".empty-state");
|
||
if (emptyState) {
|
||
emptyState.remove();
|
||
}
|
||
|
||
const systemDiv = document.createElement("div");
|
||
systemDiv.className = "system-message";
|
||
systemDiv.textContent = content;
|
||
chatMessages.appendChild(systemDiv);
|
||
|
||
setTimeout(() => {
|
||
chatMessages.scrollTop = chatMessages.scrollHeight;
|
||
}, 100);
|
||
}
|
||
|
||
function sendMessage() {
|
||
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
||
showAlert("Please connect to the server first", "error");
|
||
return;
|
||
}
|
||
|
||
if (!currentRecipient) {
|
||
showAlert("Please select a user to chat with", "error");
|
||
return;
|
||
}
|
||
|
||
const input = document.getElementById("messageInput");
|
||
const content = input.value.trim();
|
||
|
||
if (!content) return;
|
||
|
||
const message = {
|
||
id: generateId(),
|
||
from_user: currentUser,
|
||
to_user: currentRecipient,
|
||
content: content,
|
||
timestamp: Date.now(),
|
||
message_type: "text",
|
||
};
|
||
|
||
console.log("Sending message:", message);
|
||
ws.send(JSON.stringify(message));
|
||
input.value = "";
|
||
input.style.height = "auto";
|
||
|
||
setTimeout(() => loadChats(), 100);
|
||
}
|
||
|
||
function generateId() {
|
||
return (
|
||
Date.now().toString(36) +
|
||
Math.random().toString(36).substr(2)
|
||
);
|
||
}
|
||
|
||
function formatTime(timestamp) {
|
||
if (!timestamp) return "";
|
||
|
||
const date = new Date(timestamp);
|
||
const now = new Date();
|
||
const diff = now - date;
|
||
|
||
if (diff < 24 * 60 * 60 * 1000) {
|
||
return date.toLocaleTimeString([], {
|
||
hour: "2-digit",
|
||
minute: "2-digit",
|
||
});
|
||
} else {
|
||
return date.toLocaleDateString([], {
|
||
month: "short",
|
||
day: "numeric",
|
||
});
|
||
}
|
||
}
|
||
|
||
// Event listeners
|
||
document
|
||
.getElementById("messageInput")
|
||
.addEventListener("keydown", function (e) {
|
||
if (e.key === "Enter" && !e.shiftKey) {
|
||
e.preventDefault();
|
||
sendMessage();
|
||
}
|
||
});
|
||
|
||
document
|
||
.getElementById("loginPassword")
|
||
.addEventListener("keypress", function (e) {
|
||
if (e.key === "Enter") login();
|
||
});
|
||
|
||
document
|
||
.getElementById("registerPassword")
|
||
.addEventListener("keypress", function (e) {
|
||
if (e.key === "Enter") register();
|
||
});
|
||
|
||
// Avatar upload handler
|
||
document
|
||
.getElementById("avatarInput")
|
||
.addEventListener("change", function (e) {
|
||
const file = e.target.files[0];
|
||
if (file) {
|
||
// Validate file type
|
||
if (!file.type.startsWith("image/")) {
|
||
showAlert("Please select an image file", "error");
|
||
return;
|
||
}
|
||
|
||
// Validate file size (5MB)
|
||
if (file.size > 5 * 1024 * 1024) {
|
||
showAlert("Image must be less than 5MB", "error");
|
||
return;
|
||
}
|
||
|
||
uploadAvatar(file);
|
||
e.target.value = ""; // Reset input
|
||
}
|
||
});
|
||
|
||
// Close modals when clicking outside
|
||
document
|
||
.getElementById("profileModal")
|
||
.addEventListener("click", function (e) {
|
||
if (e.target === this) {
|
||
closeProfileModal();
|
||
}
|
||
});
|
||
|
||
document
|
||
.getElementById("deleteModal")
|
||
.addEventListener("click", function (e) {
|
||
if (e.target === this) {
|
||
closeDeleteModal();
|
||
}
|
||
});
|
||
|
||
// Close chats panel when clicking on overlay (mobile)
|
||
document
|
||
.getElementById("chatsPanel")
|
||
.addEventListener("click", function (e) {
|
||
if (e.target === this && isMobile) {
|
||
hideChatsPanel();
|
||
}
|
||
});
|
||
|
||
document.getElementById("loginPhone").focus();
|
||
</script>
|
||
</body>
|
||
</html>
|