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'), chatBottomBar: document.getElementById('chat-bottom-bar'), chatBottomBarBtn: document.getElementById('chat-bottom-bar-btn'), chatInputPanel: document.getElementById('chat-input-panel'), attachmentInput: document.getElementById('attachment-input'), attachmentBtn: document.getElementById('attachment-btn'), }; 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'; } }, async renderChatList(chats, activeChat, onChatSelect) { elements.chatList.innerHTML = ''; chats.sort((a, b) => b.timestamp - a.timestamp); if (chats.length === 0) { elements.chatList.innerHTML = `
  • No chats found
  • `; return; } for (const chat of chats) { 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 = `
    ${initials}
    ${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, chatId) { 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, chatId); } lucide.createIcons(); this.scrollToBottom(); }, /** * Append a single message (used for optimistic updates immediately upon sending) */ appendSingleMessage(msg, activeChatName, userID, chatId, isLocal = false) { elements.messagesContainer.appendChild(this.generateMessage(msg, activeChatName, userID, chatId, isLocal)) }, generateTempMessageLink(msg) { const a = document.createElement('a'); a.target = "_blank"; a.href = msg.media.url; if (msg._data?.mimetype?.startsWith('image/')) { const img = document.createElement('img'); img.classList.add('message-image-attachement'); img.src = msg.media.url; a.appendChild(img); } else { a.textContent = msg.media.filename || "Download file"; a.download = msg.media.filename; } return a; }, generateMessage(msg, activeChatName, userID, chatId, isLocal = false) { 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 if (msg.status === 'sending') { 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.hasMedia) { let a; if (isLocal) { a = this.generateTempMessageLink(msg); } else { a = document.createElement('a'); a.innerText = `[Request media]`; a.target = "_blank"; const clickListener = async (e) => { a.removeEventListener('click', clickListener); a.innerText = `[Downloading]`; const mediaMsg = msg.media ? msg : await waha.getSingleChatMessage(chatId, msg.id, true); console.log(mediaMsg.media.url); const url = new URL(mediaMsg.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 ${mediaMsg.media.filename}`; } } a.addEventListener('click', clickListener); } contentEl.appendChild(a); if (!isLocal && msg._data?.mimetype?.startsWith('image/')) { a.click(); } } const meta = document.createElement('div'); meta.className = 'message-meta'; meta.innerHTML = `${timeStr}${statusCheck}`; bubble.appendChild(meta); let uid = ""; function getPrevMessageElem() { return elements.messagesContainer.lastElementChild; } 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); return groupDiv; }, updateMessage(originalMsgId, generatedMsg) { const originalMsg = document.querySelector(`#${originalMsgId}`); if (originalMsg) { originalMsg.replaceWith(generatedMsg) } }, updateMessageTick(id, status) { let statusCheck; if (status === 'read') { statusCheck = ''; } else if (status === 'delivered') { statusCheck = ''; } else if (status === 'sending') { statusCheck = ''; } else { statusCheck = ''; } document.getElementById(id).querySelector('.message-meta').outerHTML = statusCheck; }, toggleChatBottomBar() { elements.chatBottomBar.classList.toggle("collapsed"); } };