Spaces:
Running
Running
| <html lang="fa" dir="rtl"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> | |
| <title>چت روم مینیمال | Minimal Chat</title> | |
| <!-- Importing Vazirmatn Font --> | |
| <link href="https://cdn.jsdelivr.net/gh/rastikerdar/vazirmatn@v33.003/Vazirmatn-font-face.css" rel="stylesheet" type="text/css" /> | |
| <!-- Importing FontAwesome for Icons --> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| :root { | |
| --primary: #6366f1; | |
| --primary-dark: #4f46e5; | |
| --secondary: #ec4899; | |
| --bg-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| --glass-bg: rgba(255, 255, 255, 0.1); | |
| --glass-border: rgba(255, 255, 255, 0.2); | |
| --glass-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37); | |
| --text-main: #ffffff; | |
| --text-muted: #e2e8f0; | |
| --danger: #ef4444; | |
| --success: #10b981; | |
| --msg-sent: rgba(99, 102, 241, 0.3); | |
| --msg-received: rgba(255, 255, 255, 0.1); | |
| } | |
| * { | |
| box-sizing: border-box; | |
| margin: 0; | |
| padding: 0; | |
| -webkit-tap-highlight-color: transparent; | |
| } | |
| body { | |
| font-family: 'Vazirmatn', sans-serif; | |
| background: var(--bg-gradient); | |
| color: var(--text-main); | |
| height: 100vh; | |
| overflow: hidden; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| } | |
| /* --- Glass Container --- */ | |
| .app-container { | |
| width: 100%; | |
| height: 100%; | |
| max-width: 480px; /* Mobile App feel */ | |
| background: var(--glass-bg); | |
| backdrop-filter: blur(12px); | |
| -webkit-backdrop-filter: blur(12px); | |
| border: 1px solid var(--glass-border); | |
| border-radius: 20px; | |
| box-shadow: var(--glass-shadow); | |
| position: relative; | |
| overflow: hidden; | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| @media (min-width: 481px) { | |
| .app-container { | |
| height: 90vh; | |
| margin: 20px; | |
| } | |
| } | |
| /* --- Header --- */ | |
| header { | |
| padding: 15px 20px; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| background: rgba(0, 0, 0, 0.1); | |
| border-bottom: 1px solid var(--glass-border); | |
| z-index: 10; | |
| } | |
| .header-title { | |
| font-size: 1.1rem; | |
| font-weight: 700; | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| } | |
| .status-dot { | |
| width: 10px; | |
| height: 10px; | |
| background: var(--success); | |
| border-radius: 50%; | |
| box-shadow: 0 0 10px var(--success); | |
| } | |
| .built-with { | |
| font-size: 0.7rem; | |
| color: rgba(255, 255, 255, 0.5); | |
| text-decoration: none; | |
| margin-top: 5px; | |
| display: block; | |
| text-align: center; | |
| } | |
| /* --- Screens Management --- */ | |
| .screen { | |
| display: none; | |
| flex: 1; | |
| flex-direction: column; | |
| height: 100%; | |
| animation: fadeIn 0.4s ease; | |
| } | |
| .screen.active { | |
| display: flex; | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; transform: translateY(10px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| /* --- Auth Screen --- */ | |
| .auth-screen { | |
| justify-content: center; | |
| align-items: center; | |
| padding: 30px; | |
| text-align: center; | |
| } | |
| .auth-icon { | |
| font-size: 3rem; | |
| margin-bottom: 20px; | |
| color: var(--primary); | |
| text-shadow: 0 0 20px rgba(99, 102, 241, 0.5); | |
| } | |
| .auth-input { | |
| width: 100%; | |
| padding: 15px; | |
| margin: 10px 0; | |
| background: rgba(255, 255, 255, 0.1); | |
| border: 1px solid var(--glass-border); | |
| border-radius: 12px; | |
| color: white; | |
| font-family: 'Vazirmatn', sans-serif; | |
| font-size: 1rem; | |
| text-align: center; | |
| outline: none; | |
| transition: 0.3s; | |
| } | |
| .auth-input:focus { | |
| background: rgba(255, 255, 255, 0.2); | |
| border-color: var(--primary); | |
| } | |
| .btn-primary { | |
| width: 100%; | |
| padding: 15px; | |
| background: var(--primary); | |
| border: none; | |
| border-radius: 12px; | |
| color: white; | |
| font-family: 'Vazirmatn', sans-serif; | |
| font-size: 1rem; | |
| font-weight: bold; | |
| cursor: pointer; | |
| transition: 0.3s; | |
| margin-top: 10px; | |
| } | |
| .btn-primary:active { | |
| transform: scale(0.98); | |
| background: var(--primary-dark); | |
| } | |
| /* --- Room List Screen --- */ | |
| .room-list { | |
| padding: 20px; | |
| overflow-y: auto; | |
| flex: 1; | |
| } | |
| .room-card { | |
| background: rgba(255, 255, 255, 0.1); | |
| border: 1px solid var(--glass-border); | |
| padding: 15px; | |
| border-radius: 15px; | |
| margin-bottom: 15px; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| cursor: pointer; | |
| transition: 0.3s; | |
| } | |
| .room-card:hover { | |
| background: rgba(255, 255, 255, 0.2); | |
| transform: translateX(-5px); | |
| } | |
| .room-info h3 { | |
| font-size: 1rem; | |
| margin-bottom: 5px; | |
| } | |
| .room-info p { | |
| font-size: 0.8rem; | |
| color: var(--text-muted); | |
| } | |
| .add-room-btn { | |
| width: 100%; | |
| padding: 20px; | |
| border: 2px dashed rgba(255, 255, 255, 0.3); | |
| border-radius: 15px; | |
| background: transparent; | |
| color: white; | |
| font-size: 1.2rem; | |
| cursor: pointer; | |
| transition: 0.3s; | |
| } | |
| .add-room-btn:hover { | |
| border-color: var(--primary); | |
| background: rgba(99, 102, 241, 0.1); | |
| } | |
| /* --- Chat Screen --- */ | |
| .chat-header { | |
| padding: 10px 15px; | |
| background: rgba(0, 0, 0, 0.2); | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| border-bottom: 1px solid var(--glass-border); | |
| } | |
| .chat-back { | |
| font-size: 1.2rem; | |
| cursor: pointer; | |
| padding: 5px; | |
| } | |
| .chat-messages { | |
| flex: 1; | |
| padding: 15px; | |
| overflow-y: auto; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 10px; | |
| scroll-behavior: smooth; | |
| } | |
| .message { | |
| max-width: 80%; | |
| padding: 10px 15px; | |
| border-radius: 18px; | |
| font-size: 0.95rem; | |
| position: relative; | |
| word-wrap: break-word; | |
| animation: popIn 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); | |
| } | |
| @keyframes popIn { | |
| from { opacity: 0; transform: scale(0.8); } | |
| to { opacity: 1; transform: scale(1); } | |
| } | |
| .message.sent { | |
| align-self: flex-end; | |
| background: var(--msg-sent); | |
| border-bottom-left-radius: 4px; | |
| border: 1px solid rgba(99, 102, 241, 0.3); | |
| } | |
| .message.received { | |
| align-self: flex-start; | |
| background: var(--msg-received); | |
| border-bottom-right-radius: 4px; | |
| border: 1px solid rgba(255, 255, 255, 0.1); | |
| } | |
| .message-meta { | |
| display: flex; | |
| justify-content: flex-end; | |
| align-items: center; | |
| gap: 5px; | |
| margin-top: 5px; | |
| font-size: 0.7rem; | |
| color: rgba(255, 255, 255, 0.6); | |
| } | |
| .message-actions { | |
| display: flex; | |
| gap: 8px; | |
| opacity: 0; | |
| transition: 0.2s; | |
| } | |
| .message:hover .message-actions { | |
| opacity: 1; | |
| } | |
| .action-btn { | |
| background: none; | |
| border: none; | |
| color: rgba(255, 255, 255, 0.6); | |
| cursor: pointer; | |
| padding: 2px; | |
| } | |
| .action-btn:hover { | |
| color: white; | |
| } | |
| /* --- Reply Preview --- */ | |
| .reply-preview { | |
| background: rgba(0, 0, 0, 0.2); | |
| padding: 8px 15px; | |
| border-radius: 10px; | |
| margin-bottom: 10px; | |
| font-size: 0.8rem; | |
| display: none; | |
| align-items: center; | |
| justify-content: space-between; | |
| border-right: 3px solid var(--primary); | |
| } | |
| /* --- Chat Input Area --- */ | |
| .chat-input-area { | |
| padding: 10px; | |
| background: rgba(0, 0, 0, 0.2); | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| } | |
| .emoji-trigger { | |
| font-size: 1.5rem; | |
| cursor: pointer; | |
| padding: 5px; | |
| } | |
| .chat-input { | |
| flex: 1; | |
| background: rgba(255, 255, 255, 0.1); | |
| border: none; | |
| padding: 12px 15px; | |
| border-radius: 25px; | |
| color: white; | |
| font-family: 'Vazirmatn', sans-serif; | |
| outline: none; | |
| } | |
| .send-btn { | |
| background: var(--primary); | |
| width: 45px; | |
| height: 45px; | |
| border-radius: 50%; | |
| border: none; | |
| color: white; | |
| font-size: 1.2rem; | |
| cursor: pointer; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| transition: 0.3s; | |
| } | |
| .send-btn:hover { | |
| background: var(--primary-dark); | |
| transform: scale(1.1); | |
| } | |
| /* --- Emoji Picker --- */ | |
| .emoji-picker { | |
| position: absolute; | |
| bottom: 80px; | |
| left: 20px; | |
| background: rgba(0, 0, 0, 0.8); | |
| backdrop-filter: blur(10px); | |
| padding: 15px; | |
| border-radius: 15px; | |
| display: grid; | |
| grid-template-columns: repeat(6, 1fr); | |
| gap: 10px; | |
| z-index: 100; | |
| display: none; /* Hidden by default */ | |
| border: 1px solid var(--glass-border); | |
| } | |
| .emoji-picker.active { | |
| display: grid; | |
| } | |
| .emoji-btn { | |
| font-size: 1.5rem; | |
| cursor: pointer; | |
| transition: 0.2s; | |
| } | |
| .emoji-btn:hover { | |
| transform: scale(1.2); | |
| } | |
| /* --- Modals --- */ | |
| .modal-overlay { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: rgba(0, 0, 0, 0.6); | |
| backdrop-filter: blur(5px); | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| z-index: 200; | |
| opacity: 0; | |
| pointer-events: none; | |
| transition: 0.3s; | |
| } | |
| .modal-overlay.active { | |
| opacity: 1; | |
| pointer-events: all; | |
| } | |
| .modal { | |
| background: #1e1e2e; | |
| width: 85%; | |
| padding: 25px; | |
| border-radius: 20px; | |
| border: 1px solid var(--glass-border); | |
| transform: scale(0.9); | |
| transition: 0.3s; | |
| } | |
| .modal-overlay.active .modal { | |
| transform: scale(1); | |
| } | |
| .modal h2 { | |
| margin-bottom: 15px; | |
| color: white; | |
| } | |
| .modal input { | |
| width: 100%; | |
| padding: 12px; | |
| margin-bottom: 20px; | |
| background: rgba(255, 255, 255, 0.05); | |
| border: 1px solid rgba(255, 255, 255, 0.1); | |
| border-radius: 10px; | |
| color: white; | |
| font-family: 'Vazirmatn', sans-serif; | |
| } | |
| .modal-actions { | |
| display: flex; | |
| gap: 10px; | |
| } | |
| .btn-cancel { | |
| flex: 1; | |
| padding: 12px; | |
| background: rgba(255, 255, 255, 0.1); | |
| border: none; | |
| border-radius: 10px; | |
| color: white; | |
| cursor: pointer; | |
| } | |
| .btn-confirm { | |
| flex: 1; | |
| padding: 12px; | |
| background: var(--primary); | |
| border: none; | |
| border-radius: 10px; | |
| color: white; | |
| cursor: pointer; | |
| } | |
| /* --- Toast Notification --- */ | |
| .toast { | |
| position: absolute; | |
| top: 20px; | |
| left: 50%; | |
| transform: translateX(-50%) translateY(-20px); | |
| background: rgba(0, 0, 0, 0.8); | |
| padding: 10px 20px; | |
| border-radius: 20px; | |
| font-size: 0.9rem; | |
| opacity: 0; | |
| transition: 0.3s; | |
| pointer-events: none; | |
| z-index: 300; | |
| white-space: nowrap; | |
| } | |
| .toast.show { | |
| opacity: 1; | |
| transform: translateX(-50%) translateY(0); | |
| } | |
| /* --- Reactions --- */ | |
| .reaction-bubble { | |
| position: absolute; | |
| top: -20px; | |
| left: 50%; | |
| transform: translateX(-50%) scale(0); | |
| font-size: 1.5rem; | |
| transition: 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); | |
| pointer-events: none; | |
| z-index: 50; | |
| } | |
| .reaction-bubble.show { | |
| transform: translateX(-50%) scale(1); | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="app-container"> | |
| <header> | |
| <div class="header-title"> | |
| <div class="status-dot"></div> | |
| <span id="app-title">چت روم</span> | |
| </div> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="built-with">Built with anycoder</a> | |
| </header> | |
| <!-- Toast Notification --> | |
| <div id="toast" class="toast">پیام سیستم</div> | |
| <!-- Screen 1: Authentication --> | |
| <div id="auth-screen" class="screen active auth-screen"> | |
| <i class="fas fa-comments auth-icon"></i> | |
| <h2 style="margin-bottom: 20px;">خوش آمدید</h2> | |
| <input type="tel" id="phone-input" class="auth-input" placeholder="شماره موبایل خود را وارد کنید" maxlength="11"> | |
| <button class="btn-primary" onclick="App.auth.login()">ورود / ثبتنام</button> | |
| </div> | |
| <!-- Screen 2: Room List --> | |
| <div id="room-screen" class="screen"> | |
| <div class="room-list"> | |
| <div id="rooms-container"> | |
| <!-- Rooms injected here --> | |
| </div> | |
| <button class="add-room-btn" onclick="App.rooms.openCreateModal()"> | |
| <i class="fas fa-plus"></i> ایجاد اتاق جدید | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Screen 3: Chat Room --> | |
| <div id="chat-screen" class="screen"> | |
| <div class="chat-header"> | |
| <i class="fas fa-arrow-right chat-back" onclick="App.ui.navigate('room-screen')"></i> | |
| <div style="flex:1"> | |
| <h3 id="chat-room-name" style="font-size: 1rem;">نام اتاق</h3> | |
| <p id="chat-room-count" style="font-size: 0.7rem; color: var(--text-muted);">0 نفر آنلاین</p> | |
| </div> | |
| <i class="fas fa-share-alt" style="cursor: pointer;" onclick="App.chat.copyLink()" title="کپی لینک"></i> | |
| </div> | |
| <div class="chat-messages" id="chat-messages"> | |
| <!-- Messages injected here --> | |
| </div> | |
| <!-- Reply Preview --> | |
| <div id="reply-preview" class="reply-preview"> | |
| <span id="reply-text">پاسخ به...</span> | |
| <i class="fas fa-times" style="cursor: pointer;" onclick="App.chat.cancelReply()"></i> | |
| </div> | |
| <!-- Emoji Picker --> | |
| <div class="emoji-picker" id="emoji-picker"> | |
| <div class="emoji-btn" onclick="App.chat.insertEmoji('😀')">😀</div> | |
| <div class="emoji-btn" onclick="App.chat.insertEmoji('😂')">😂</div> | |
| <div class="emoji-btn" onclick="App.chat.insertEmoji('😍')">😍</div> | |
| <div class="emoji-btn" onclick="App.chat.insertEmoji('😎')">😎</div> | |
| <div class="emoji-btn" onclick="App.chat.insertEmoji('🤔')">🤔</div> | |
| <div class="emoji-btn" onclick="App.chat.insertEmoji('👍')">👍</div> | |
| <div class="emoji-btn" onclick="App.chat.insertEmoji('👎')">👎</div> | |
| <div class="emoji-btn" onclick="App.chat.insertEmoji('🎉')">🎉</div> | |
| <div class="emoji-btn" onclick="App.chat.insertEmoji('❤️')">❤️</div> | |
| <div class="emoji-btn" onclick="App.chat.insertEmoji('🔥')">🔥</div> | |
| <div class="emoji-btn" onclick="App.chat.insertEmoji('✨')">✨</div> | |
| <div class="emoji-btn" onclick="App.chat.insertEmoji('👋')">👋</div> | |
| </div> | |
| <div class="chat-input-area"> | |
| <i class="fas fa-smile emoji-trigger" onclick="App.ui.toggleEmojiPicker()"></i> | |
| <input type="text" id="message-input" class="chat-input" placeholder="پیام خود را بنویسید..." onkeypress="App.chat.handleKeyPress(event)"> | |
| <button class="send-btn" onclick="App.chat.sendMessage()"> | |
| <i class="fas fa-paper-plane"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Create Room Modal --> | |
| <div id="create-room-modal" class="modal-overlay"> | |
| <div class="modal"> | |
| <h2>ساخت اتاق جدید</h2> | |
| <input type="text" id="new-room-name" placeholder="نام اتاق را وارد کنید"> | |
| <div class="modal-actions"> | |
| <button class="btn-cancel" onclick="App.rooms.closeModal()">انصراف</button> | |
| <button class="btn-confirm" onclick="App.rooms.createRoom()">ساخت</button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Delete Confirmation Modal --> | |
| <div id="delete-modal" class="modal-overlay"> | |
| <div class="modal"> | |
| <h2>حذف پیام</h2> | |
| <p style="margin-bottom: 20px; color: #ccc;">آیا از حذف این پیام اطمینان دارید؟</p> | |
| <div class="modal-actions"> | |
| <button class="btn-cancel" onclick="App.chat.closeDeleteModal()">انصراف</button> | |
| <button class="btn-confirm" style="background: var(--danger);" onclick="App.chat.confirmDelete()">حذف</button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| /** | |
| * Main Application Logic | |
| * Implements modular structure within a single file | |
| */ | |
| const App = { | |
| state: { | |
| user: null, | |
| currentRoomId: null, | |
| rooms: [], | |
| messages: [], | |
| replyingTo: null, | |
| deletingMessageId: null | |
| }, | |
| init: function() { | |
| // Load data from localStorage or initialize defaults | |
| const savedRooms = localStorage.getItem('chat_rooms'); | |
| const savedMessages = localStorage.getItem('chat_messages'); | |
| if (savedRooms) { | |
| this.state.rooms = JSON.parse(savedRooms); | |
| } else { | |
| // Default rooms | |
| this.state.rooms = [ | |
| { id: 1, name: 'گفتگوی عمومی', lastMsg: 'سلام همه دوستان!', time: '10:30' }, | |
| { id: 2, name: 'تکنولوژی', lastMsg: 'کدام گوشی را پیشنهاد میکنید؟', time: '09:15' } | |
| ]; | |
| } | |
| if (savedMessages) { | |
| this.state.messages = JSON.parse(savedMessages); | |
| } | |
| // Initialize UI | |
| this.ui.renderRooms(); | |
| // Setup Swipe Gestures | |
| this.gestures.init(); | |
| }, | |
| // --- Authentication Module --- | |
| auth: { | |
| login: function() { | |
| const phoneInput = document.getElementById('phone-input'); | |
| const phone = phoneInput.value.trim(); | |
| if (phone.length < 10) { | |
| App.ui.showToast('لطفاً شماره موبایل معتبر وارد کنید'); | |
| return; | |
| } | |
| // Simulate API Call | |
| App.state.user = { phone: phone, name: 'کاربر ' + phone.slice(-4) }; | |
| App.ui.showToast(`خوش آمدید، ${App.state.user.name}`); | |
| App.ui.navigate('room-screen'); | |
| } | |
| }, | |
| // --- Room Management Module --- | |
| rooms: { | |
| openCreateModal: function() { | |
| document.getElementById('create-room-modal').classList.add('active'); | |
| document.getElementById('new-room-name').focus(); | |
| }, | |
| closeModal: function() { | |
| document.getElementById('create-room-modal').classList.remove('active'); | |
| document.getElementById('new-room-name').value = ''; | |
| }, | |
| createRoom: function() { | |
| const nameInput = document.getElementById('new-room-name'); | |
| const name = nameInput.value.trim(); | |
| if (!name) { | |
| App.ui.showToast('نام اتاق نمیتواند خالی باشد'); | |
| return; | |
| } | |
| const newRoom = { | |
| id: Date.now(), | |
| name: name, | |
| lastMsg: 'اتاق ساخته شد', | |
| time: new Date().toLocaleTimeString('fa-IR', {hour: '2-digit', minute:'2-digit'}) | |
| }; | |
| App.state.rooms.unshift(newRoom); // Add to top | |
| App.utils.saveData(); | |
| App.ui.renderRooms(); | |
| App.rooms.closeModal(); | |
| App.ui.showToast('اتاق با موفقیت ساخته شد'); | |
| } | |
| }, | |
| // --- Chat Module --- | |
| chat: { | |
| sendMessage: function() { | |
| const input = document.getElementById('message-input'); | |
| const text = input.value.trim(); | |
| if (!text) return; | |
| const msgData = { | |
| id: Date.now(), | |
| roomId: App.state.currentRoomId, | |
| sender: App.state.user.name, | |
| text: text, | |
| timestamp: new Date().toISOString(), | |
| reactions: [] | |
| }; | |
| // Add to local state | |
| App.state.messages.push(msgData); | |
| App.utils.saveData(); | |
| // Update room metadata | |
| const roomIndex = App.state.rooms.findIndex(r => r.id === App.state.currentRoomId); | |
| if (roomIndex > -1) { | |
| App.state.rooms[roomIndex].lastMsg = text; | |
| App.state.rooms[roomIndex].time = new Date().toLocaleTimeString('fa-IR', {hour: '2-digit', minute:'2-digit'}); | |
| App.utils.saveData(); | |
| } | |
| // Render | |
| App.ui.renderMessages(); | |
| App.ui.renderRooms(); // Update list preview | |
| input.value = ''; | |
| App.chat.cancelReply(); | |
| }, | |
| handleKeyPress: function(e) { | |
| if (e.key === 'Enter') { | |
| App.chat.sendMessage(); | |
| } | |
| }, | |
| openRoom: function(roomId) { | |
| App.state.currentRoomId = roomId; | |
| const room = App.state.rooms.find(r => r.id === roomId); | |
| document.getElementById('chat-room-name').innerText = room.name; | |
| document.getElementById('chat-room-count').innerText = Math.floor(Math.random() * 20 + 5) + ' نفر آنلاین'; | |
| App.ui.navigate('chat-screen'); | |
| App.ui.renderMessages(); | |
| }, | |
| renderMessages: function() { | |
| const container = document.getElementById('chat-messages'); | |
| container.innerHTML = ''; | |
| const roomMessages = App.state.messages.filter(m => m.roomId === App.state.currentRoomId); | |
| if (roomMessages.length === 0) { | |
| container.innerHTML = '<div style="text-align:center; opacity:0.5; margin-top:20px;">هنوز پیامی وجود ندارد. شروع کنید!</div>'; | |
| return; | |
| } | |
| roomMessages.forEach(msg => { | |
| const isSent = msg.sender === App.state.user.name; | |
| const timeString = new Date(msg.timestamp).toLocaleTimeString('fa-IR', {hour: '2-digit', minute:'2-digit'}); | |
| const msgEl = document.createElement('div'); | |
| msgEl.className = `message ${isSent ? 'sent' : 'received'}`; | |
| msgEl.innerHTML = ` | |
| ${msg.text} | |
| <div class="message-meta"> | |
| <span>${timeString}</span> | |
| <div class="message-actions"> | |
| <button class="action-btn" onclick="App.chat.startReply(${msg.id})" title="پاسخ"><i class="fas fa-reply"></i></button> | |
| <button class="action-btn" onclick="App.chat.openDeleteModal(${msg.id})" title="حذف"><i class="fas fa-trash"></i></button> | |
| </div> | |
| </div> | |
| `; | |
| // Reaction click listener | |
| msgEl.addEventListener('click', (e) => { | |
| if(!e.target.closest('.message-actions')) { | |
| App.chat.showReactionPopup(msg.id); | |
| } | |
| }); | |
| container.appendChild(msgEl); | |
| }); | |
| // Scroll to bottom | |
| container.scrollTop = container.scrollHeight; | |
| }, | |
| // Reply Functionality | |
| startReply: function(msgId) { | |
| const msg = App.state.messages.find(m => m.id === msgId); | |
| App.state.replyingTo = msg; | |
| const preview = document.getElementById('reply-preview'); | |
| const previewText = document.getElementById('reply-text'); | |
| previewText.innerText = `پاسخ به: ${msg.text}`; | |
| preview.style.display = 'flex'; | |
| document.getElementById('message-input').focus(); | |
| }, | |
| cancelReply: function() { | |
| App.state.replyingTo = null; | |
| document.getElementById('reply-preview').style.display = 'none'; | |
| }, | |
| // Delete Functionality | |
| openDeleteModal: function(msgId) { | |
| App.state.deletingMessageId = msgId; | |
| document.getElementById('delete-modal').classList.add('active'); | |
| }, | |
| closeDeleteModal: function() { | |
| App.state.deletingMessageId = null; | |
| document.getElementById('delete-modal').classList.remove('active'); | |
| }, | |
| confirmDelete: function() { | |
| if (App.state.deletingMessageId) { | |
| App.state.messages = App.state.messages.filter(m => m.id !== App.state.deletingMessageId); | |
| App.utils.saveData(); | |
| App.ui.renderMessages(); | |
| App.ui.showToast('پیام حذف شد'); | |
| } | |
| App.chat.closeDeleteModal(); | |
| }, | |
| // Emoji Functionality | |
| insertEmoji: function(emoji) { | |
| const input = document.getElementById('message-input'); | |
| input.value += emoji; | |
| input.focus(); | |
| }, | |
| showReactionPopup: function(msgId) { | |
| const emojis = ['👍', '❤️', '😂', '😮', '😢', '😡']; | |
| const reactionsContainer = document.createElement('div'); | |
| reactionsContainer.style.position = 'absolute'; | |
| reactionsContainer.style.bottom = '20px'; | |
| reactionsContainer.style.left = '50%'; | |
| reactionsContainer.style.transform = 'translateX(-50%)'; | |
| reactionsContainer.style.display = 'flex'; | |
| reactionsContainer.style.gap = '10px'; | |
| reactionsContainer.style.background = 'rgba(0,0,0,0.7)'; | |
| reactionsContainer.style.padding = '10px'; | |
| reactionsContainer.style.borderRadius = '30px'; | |
| reactionsContainer.style.zIndex = '100'; | |
| emojis.forEach(emoji => { | |
| const btn = document.createElement('button'); | |
| btn.innerText = emoji; | |
| btn.style.background = 'none'; | |
| btn.style.border = 'none'; | |
| btn.style.fontSize = '1.5rem'; | |
| btn.style.cursor = 'pointer'; | |
| btn.onclick = () => App.chat.addReaction(msgId, emoji); | |
| reactionsContainer.appendChild(btn); | |
| }); | |
| document.getElementById('chat-screen').appendChild(reactionsContainer); | |
| // Remove after 2 seconds | |
| setTimeout(() => { | |
| reactionsContainer.remove(); | |
| }, 2000); | |
| }, | |
| addReaction: function(msgId, emoji) { | |
| const msg = App.state.messages.find(m => m.id === msgId); | |
| if (msg) { | |
| // Simple reaction logic: just store it for now | |
| // In a real app, we'd track users | |
| App.ui.showToast(`واکنش ${emoji} ثبت شد`); | |
| } | |
| }, | |
| copyLink: function() { | |
| const dummyLink = `${window.location.origin}?room=${App.state.currentRoomId}`; | |
| navigator.clipboard.writeText(dummyLink).then(() => { | |
| App.ui.showToast('لینک اتاق کپی شد'); | |
| }); | |
| } | |
| }, | |
| // --- UI Module --- | |
| ui: { | |
| navigate: function(screenId) { | |
| document.querySelectorAll('.screen').forEach(s => s.classList.remove('active')); | |
| document.getElementById(screenId).classList.add('active'); | |
| }, | |
| renderRooms: function() { | |
| const container = document.getElementById('rooms-container'); | |
| container.innerHTML = ''; | |
| App.state.rooms.forEach(room => { | |
| const card = document.createElement('div'); | |
| card.className = 'room-card'; | |
| card.onclick = () => App.chat.openRoom(room.id); | |
| card.innerHTML = ` | |
| <div class="room-info"> | |
| <h3>${room.name}</h3> | |
| <p>${room.lastMsg}</p> | |
| </div> | |
| <div style="text-align:left; color:var(--text-muted); font-size:0.8rem;"> | |
| <div>${room.time}</div> | |
| <i class="fas fa-chevron-left"></i> | |
| </div> | |
| `; | |
| container.appendChild(card); | |
| }); | |
| }, | |
| renderMessages: function() { | |
| App.chat.renderMessages(); | |
| }, | |
| toggleEmojiPicker: function() { | |
| const picker = document.getElementById('emoji-picker'); | |
| picker.classList.toggle('active'); | |
| }, | |
| showToast: function(message) { | |
| const toast = document.getElementById('toast'); | |
| toast.innerText = message; | |
| toast.classList.add('show'); | |
| setTimeout(() => { | |
| toast.classList.remove('show'); | |
| }, 3000); | |
| } | |
| }, | |
| // --- Gestures Module --- | |
| gestures: { | |
| init: function() { | |
| let startX = 0; | |
| let startY = 0; | |
| const container = document.querySelector('.app-container'); | |
| container.addEventListener('touchstart', (e) => { | |
| startX = e.changedTouches[0].screenX; | |
| startY = e.changedTouches[0].screenY; | |
| }, {passive: true}); | |
| container.addEventListener('touchend', (e) => { | |
| const endX = e.changedTouches[0].screenX; | |
| const endY = e.changedTouches[0].screenY; | |
| const diffX = startX - endX; | |
| const diffY = startY - endY; | |
| // Swipe Right to go back (common mobile pattern) | |
| if (Math.abs(diffX) > 50 && Math.abs(diffX) > Math.abs(diffY)) { | |
| if (diffX > 0) { | |
| // Swipe Right | |
| if (document.getElementById('chat-screen').classList.contains('active')) { | |
| App.ui.navigate('room-screen'); | |
| } | |
| } | |
| } | |
| }); | |
| } | |
| }, | |
| // --- Utilities --- | |
| utils: { | |
| saveData: function() { | |
| localStorage.setItem('chat_rooms', JSON.stringify(App.state.rooms)); | |
| localStorage.setItem('chat_messages', JSON.stringify(App.state.messages)); | |
| } | |
| } | |
| }; | |
| // Start App | |
| document.addEventListener('DOMContentLoaded', () => { | |
| App.init(); | |
| }); | |
| </script> | |
| </body> | |
| </html> |