import { formatTime } from "./utils.js"; import { waha } from "./waha.js"; import { config } from "./config.js"; // DOM Selector Cache export const elements = { chatList: document.getElementById('chat-list'), chatsLoader: document.getElementById('chats-loader'), chatSearch: document.getElementById('chat-search'), refreshChatsBtn: document.getElementById('refresh-chats-btn'), backendStatusText: document.getElementById('backend-status-text'), apiStatusIndicator: document.querySelector('.pulse-dot'), noChatState: document.getElementById('no-chat-state'), activeChatContainer: document.getElementById('active-chat-container'), activeChatName: document.getElementById('active-chat-name'), activeChatAvatar: document.getElementById('active-chat-avatar'), messagesContainer: document.getElementById('messages-container'), messageForm: document.getElementById('message-form'), messageInput: document.getElementById('message-input'), // backToSidebarBtn: document.getElementById('back-to-sidebar'), backToSidebarBtn: document.querySelector('.chat-header'), sidebar: document.querySelector('.sidebar'), settingsModal: document.getElementById('settings-modal'), settingsIconBtn: document.querySelector('.header-actions button[title="Settings"]'), cancelSettingsBtn: document.getElementById('cancel-settings'), saveSettingsBtn: document.getElementById('save-settings'), inputWahaUrl: document.getElementById('settings-waha-url'), inputSession: document.getElementById('settings-session'), inputApiKey: document.getElementById('settings-api-key'), loggedUserName: document.getElementById('pandora-username'), // userIcon: document.getElementById('pandora-user-icon') }; export const ui = { /** * Show or hide Settings connection modal */ toggleModal(show) { if (show) { elements.settingsModal.classList.remove('hidden'); } else { elements.settingsModal.classList.add('hidden'); } }, /** * Switch view state when a contact chat is opened or closed */ toggleChatState(hasActive) { if (hasActive) { elements.noChatState.classList.add('hidden'); elements.activeChatContainer.classList.remove('hidden'); } else { elements.noChatState.classList.remove('hidden'); elements.activeChatContainer.classList.add('hidden'); } }, /** * Scroll message list automatically to bottom */ scrollToBottom() { elements.messagesContainer.scrollTop = elements.messagesContainer.scrollHeight; }, /** * Update connection status badge in sidebar footer */ updateConnectionStatus(isConnected, text) { elements.backendStatusText.textContent = text; if (isConnected) { elements.apiStatusIndicator.style.backgroundColor = 'var(--online-color)'; elements.apiStatusIndicator.style.animation = 'pulse 1.8s infinite'; } else { elements.apiStatusIndicator.style.backgroundColor = '#ef4444'; elements.apiStatusIndicator.style.animation = 'none'; } }, /** * Render sidebar contact chats list */ renderChatList(chats, activeChat, onChatSelect) { elements.chatList.innerHTML = ''; if (chats.length === 0) { elements.chatList.innerHTML = `
  • No chats found
  • `; return; } chats.forEach(async (chat) => { const li = document.createElement('li'); li.className = `chat-item ${activeChat && activeChat.id === chat.id ? 'active' : ''}`; li.dataset.id = chat.id; const picture = await waha.getChatPicture(chat.id); const initials = chat.name ? chat.name.substring(0, 1).toUpperCase() : '?'; const hasUnread = chat.unreadCount && chat.unreadCount > 0; const timeStr = formatTime(chat.timestamp || new Date()); li.innerHTML = `
    ${chat.name} ${timeStr}
    ${chat.lastMessage || 'No messages yet'} ${hasUnread ? `${chat.unreadCount}` : ''}
    `; li.addEventListener('click', () => onChatSelect(chat)); elements.chatList.appendChild(li); }); }, /** * Render chat message log inside chat view container */ async renderMessages(messages, activeChatName, userID) { elements.messagesContainer.innerHTML = ''; if (messages.length === 0) { elements.messagesContainer.innerHTML = '
    No messages. Say hello!
    '; return; } for (const msg of messages) { this.appendSingleMessage(msg, activeChatName, userID); } lucide.createIcons(); this.scrollToBottom(); }, /** * Append a single message (used for optimistic updates immediately upon sending) */ appendSingleMessage(msg, activeChatName, userID) { console.log(msg); const isOutgoing = msg.fromMe || msg.sender === 'me'; const groupDiv = document.createElement('div'); groupDiv.className = `message-group ${isOutgoing ? 'outgoing' : 'incoming'}`; groupDiv.id = msg.id; groupDiv.dataset.from = msg.participant || msg.from; const senderName = isOutgoing ? userID : (msg._data.notifyName || activeChatName); const timeStr = formatTime(msg.timestamp || new Date()); let statusCheck = ''; if (isOutgoing) { if (msg.status === 'read') { statusCheck = ''; } else if (msg.status === 'delivered') { statusCheck = ''; } else { statusCheck = ''; } } const bubble = document.createElement('div'); bubble.className = 'message-bubble'; if (!isOutgoing) { const senderEl = document.createElement('span'); senderEl.className = 'message-sender'; senderEl.textContent = senderName; bubble.appendChild(senderEl); } const contentEl = document.createElement('div'); contentEl.classList.add('message-content'); const textEl = document.createElement('div'); textEl.innerHTML = msg.body || msg.text || ""; contentEl.appendChild(textEl); bubble.appendChild(contentEl); if (msg.media?.url) { const a = document.createElement('a'); a.innerText = `Request media (${msg.media.filename})`; a.addEventListener('click', async (e) => { const url = new URL(msg.media.url); const reqID = url.pathname.split('/').filter(Boolean).pop(); const { blob, filename } = await waha.downloadMedia(reqID); const objectUrl = URL.createObjectURL(blob); e.target.href = objectUrl; if (blob.type.startsWith('image/')) { a.textContent = ""; const img = document.createElement('img'); img.classList.add('message-image-attachement'); img.src = objectUrl; a.appendChild(img); } else { e.target.textContent = filename || `Download ${msg.media.filename}`; } }) contentEl.appendChild(a); } const meta = document.createElement('div'); meta.className = 'message-meta'; meta.innerHTML = `${timeStr}${statusCheck}`; bubble.appendChild(meta); let uid = ""; function getPrevMessageElem() { return elements.messagesContainer.lastElementChild; // <‑‑ key change } const prevMsgEl = getPrevMessageElem(); if (isOutgoing) { if (prevMsgEl && prevMsgEl.classList.contains('outgoing')) { } else { groupDiv.appendChild(document.createElement('div')).className = 'message-indicator'; } } else if (msg.participant) { uid = msg.participant; if (!prevMsgEl || uid !== prevMsgEl.dataset.from) { groupDiv.appendChild(document.createElement('div')).className = 'message-indicator'; } } else { uid = msg.from; if (!prevMsgEl || uid !== prevMsgEl.dataset.from) { groupDiv.appendChild(document.createElement('div')).className = 'message-indicator'; } } groupDiv.appendChild(bubble); elements.messagesContainer.appendChild(groupDiv); } };