diff --git a/js/app.js b/js/app.js index 3834d72..a274c37 100644 --- a/js/app.js +++ b/js/app.js @@ -26,9 +26,9 @@ document.addEventListener('DOMContentLoaded', async () => { }); function loadChats() { + elements.chatsLoader.classList.remove('hidden'); try { - elements.chatsLoader.classList.remove('hidden'); - fetchChats().then(() => { + fetchChats().then(async () => { ui.renderChatList(getChats(), activeChatState, selectChat); const hash = window.location.hash; @@ -181,8 +181,6 @@ function initWebSocket() { async function handleIncomingMessage(msg) { if (!msg) return; - // console.log('[WS] handleIncomingMessage payload:', msg); - ui.updateChatInChatList(msg); const rawChatId = msg.chatId || msg.from || (msg.chat && msg.chat.id); @@ -201,21 +199,13 @@ async function handleIncomingMessage(msg) { const msgId = normalizeId(msg.id) || msg.id; const exists = document.getElementById(msgId); if (!exists) { + const scrolled = elements.messagesContainer.scrollTop == elements.messagesContainer.scrollTopMax; ui.appendSingleMessage({ ...msg, chatId: msgChatId }, activeChatState.name, (await getAppUser()).id); - ui.scrollToBottom(); + if (scrolled) { + ui.scrollToBottom(); + } } } - - const chatIndex = getChats().findIndex(c => c.id === msgChatId); - if (chatIndex !== -1) { - const chat = getChats()[chatIndex]; - chat.lastMessage = msg.body || msg.text || 'Media message'; - chat.timestamp = msg.timestamp ? (msg.timestamp * 1000) : Date.now(); - - if (!msg.fromMe && (!activeChatState || activeChatState.id !== msgChatId)) { - chat.unreadCount = (chat.unreadCount || 0) + 1; - } - } } async function selectChat(chat, isPopState = false, smoothScroll = true) { @@ -249,6 +239,7 @@ async function selectChat(chat, isPopState = false, smoothScroll = true) { } if (!isPopState && window.location.hash !== `#chat-${chat.id}`) { + window.location.hash = ``; window.location.hash = `chat-${chat.id}`; } @@ -394,3 +385,7 @@ async function checkWahaStatus() { ui.updateConnectionStatus(false, 'WAHA Server Offline'); } } + +export function getCurrentChat() { + return activeChatState; +} \ No newline at end of file diff --git a/js/ui.js b/js/ui.js index f5a1bc5..0ee54e2 100644 --- a/js/ui.js +++ b/js/ui.js @@ -1,5 +1,6 @@ import { formatTime, normalizeId } from "./utils.js"; import { getChatPicture, getMessage, getMedia, getMoreChatMessages } from "./storage.js"; +import { getCurrentChat } from "./app.js"; export const elements = { chatList: document.getElementById('chat-list'), @@ -43,7 +44,7 @@ export const ui = { } }) }, - + /** * Switch view state when a contact chat is opened or closed */ @@ -80,7 +81,6 @@ export const ui = { async renderChatList(chats, activeChat, onChatSelect) { elements.chatList.innerHTML = ''; - chats.sort((a, b) => b.timestamp - a.timestamp); if (chats.length === 0) { @@ -93,34 +93,61 @@ export const ui = { li.className = `chat-item ${activeChat && activeChat.id === chat.id ? 'active' : ''}`; li.dataset.id = chat.id; - const picture = await 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}` : ''} -
-
- `; +
+ ${initials} +
+
+
+ ${chat.name} + ${timeStr} +
+
+ + ${chat.lastMessage || 'No messages yet'} + + ${hasUnread ? `${chat.unreadCount}` : ''} +
+
+ `; li.addEventListener('click', () => onChatSelect(chat)); elements.chatList.appendChild(li); + + (async () => { + try { + const picture = await getChatPicture(chat.id); + const img = li.querySelector(`img[data-chat-avatar="${chat.id}"]`); + if (img) img.src = picture.url ? picture.url : ''; + } catch (e) { + } + })(); } }, async updateChatInChatList(msg) { - const span = document.querySelector(`.chat-item-msg[data-chatid="${msg.fromMe ? msg.to : msg.from}"]`); - if (span) { - span.innerText = msg.body; + const chat = document.querySelector(`.chat-item[data-id="${msg.fromMe ? msg.to : msg.from}"]`); + + if (chat) { + const messageItem = chat.querySelector('.chat-item-msg'); + const time = chat.querySelector('.chat-item-time') + messageItem.innerText = msg.body || msg.text || 'Media message'; + time.innerText = msg.timestamp ? formatTime(msg.timestamp) : Date.now(); + + const activeChatState = getCurrentChat(); + + if (!msg.fromMe && (!activeChatState || activeChatState.id !== (msg.fromMe ? msg.to : msg.from))) { + const unreadBadge = chat.querySelector('.unread-badge'); + unreadBadge.innerText = (Number(unreadBadge.innerHTML) || 0) + 1; + } } }, @@ -134,7 +161,7 @@ export const ui = { elements.messagesContainer.innerHTML = '
No messages. Say hello!
'; return; } - + const loadMore = document.createElement("button"); loadMore.classList.add("load-more-btn"); loadMore.innerText = "Load more"; @@ -149,7 +176,7 @@ export const ui = { this.scrollToBottom(); }, - + async loadMoreMessages(chatId, userId) { const oldest = document.querySelector('.message-group:first-of-type'); const oldestTimestamp = oldest.dataset.timestamp; @@ -157,17 +184,17 @@ export const ui = { console.log(oldest) console.log(oldestId) console.log(oldestTimestamp) - + const loadMoreButton = document.querySelector('.load-more-btn'); loadMoreButton.removeEventListener('click', this.loadMoreMessages); - + const msgs = await getMoreChatMessages(chatId, oldestTimestamp, oldestId); msgs.shift(); msgs.forEach(async msg => { loadMoreButton.after(this.generateMessage(msg, userId, chatId)); }); - + loadMoreButton.addEventListener('click', this.loadMoreMessages); }, @@ -229,7 +256,7 @@ export const ui = { const bubble = document.createElement('div'); bubble.className = 'message-bubble'; - + let prevUid; if (msg.participant) { @@ -237,7 +264,7 @@ export const ui = { } else { prevUid = msg.from; } - + if (!isOutgoing && (!prevMsgEl || prevUid !== prevMsgEl.dataset.from)) { const senderEl = document.createElement('span'); senderEl.className = 'message-sender'; @@ -310,17 +337,18 @@ export const ui = { if (isOutgoing) { if (prevMsgEl && prevMsgEl.classList.contains('outgoing')) { + groupDiv.classList.add('same-sender'); } else { groupDiv.appendChild(document.createElement('div')).className = 'message-indicator'; } } else if (msg.participant) { if (!prevMsgEl || prevUid !== prevMsgEl.dataset.from) { groupDiv.appendChild(document.createElement('div')).className = 'message-indicator'; - } + } else groupDiv.classList.add('same-sender'); } else { if (!prevMsgEl || prevUid !== prevMsgEl.dataset.from) { groupDiv.appendChild(document.createElement('div')).className = 'message-indicator'; - } + } else groupDiv.classList.add('same-sender'); } groupDiv.appendChild(bubble); @@ -352,7 +380,7 @@ export const ui = { toggleChatBottomBar() { elements.chatBottomBar.classList.toggle("collapsed"); }, - + removeChatMessage(msgId) { const message = document.getElementById(msgId); if (message) message.remove(); diff --git a/style.css b/style.css index 4b54b0a..831eae1 100644 --- a/style.css +++ b/style.css @@ -45,8 +45,8 @@ body { background-color: var(--bg-main); color: var(--text-primary); background-image: - radial-gradient(at 0% 0%, rgba(99, 102, 241, 0.12) 0px, transparent 50%), - radial-gradient(at 100% 100%, rgba(168, 85, 247, 0.12) 0px, transparent 50%); + radial-gradient(at 0% 0%, rgba(99, 102, 241, 0.12) 0px, transparent 50%), + radial-gradient(at 100% 100%, rgba(168, 85, 247, 0.12) 0px, transparent 50%); } a { @@ -222,6 +222,7 @@ input:focus { border: none; font-size: 0.9rem; transition: all 0.25s ease; + background-color: var(--bg-secondary); } #chat-search:focus { @@ -281,6 +282,9 @@ input:focus { .chat-item:hover { background: rgba(255, 255, 255, 0.04); + transform: scale(0.98) skewX(10deg); + filter: blur(.1px); + opacity: .6; } .chat-item-info { @@ -470,14 +474,46 @@ input:focus { height: 100%; position: relative; overflow: hidden; + z-index: 0; +} + +.active-chat-container::before { + content: ""; + position: absolute; + inset: 0; + background-image: var(--background-image); + background-size: cover; + background-position: center; + background-repeat: no-repeat; + opacity: 0.4; + pointer-events: none; + z-index: 0; +} + +.active-chat-container > * { + position: relative; + z-index: 1; } .chat-header { - padding: 16px 24px; + padding: .6em 1em; display: flex; - justify-content: space-between; - align-items: center; - background: var(--bg-main); + position: relative; +} + +.chat-header::before { + content: ""; + position: absolute; + inset: 0; + pointer-events: none; + z-index: -1; + + background: linear-gradient( + to bottom, + rgba(0, 0, 0, 0.75), + rgba(0, 0, 0, 0.35), + transparent + ); } .active-contact-info { @@ -516,14 +552,14 @@ input:focus { .messages-container { flex: 1; overflow-y: auto; - padding: 24px; + padding: 1rem; + padding-bottom: .6rem; display: flex; flex-direction: column; - gap: 16px; user-select: text; background-image: - radial-gradient(circle at 10% 20%, rgba(99, 102, 241, 0.03) 0%, transparent 40%), - radial-gradient(circle at 90% 80%, rgba(168, 85, 247, 0.03) 0%, transparent 40%); + radial-gradient(circle at 10% 20%, rgba(99, 102, 241, 0.03) 0%, transparent 40%), + radial-gradient(circle at 90% 80%, rgba(168, 85, 247, 0.03) 0%, transparent 40%); } .load-more-btn { @@ -539,6 +575,7 @@ input:focus { } .message-group { + margin-top: .8em; display: flex; flex-direction: column; max-width: 65%; @@ -552,11 +589,14 @@ input:focus { align-self: flex-end; } +.message-group.same-sender { + margin-top: .4em; +} + .message-sender { font-size: 0.75rem; font-weight: 600; color: var(--text-primary); - margin-bottom: 2px; width: 100%; display: flex; } @@ -570,8 +610,7 @@ input:focus { .message-bubble { display: flex; flex-direction: column; - gap: .4em; - padding: 12px 16px; + padding: 8px 10px; font-size: 0.92rem; line-height: 1.5; position: relative; @@ -630,6 +669,7 @@ input:focus { display: flex; align-items: center; gap: 16px; + background-color: var(--bg-main); } .alternate-panel { @@ -825,15 +865,15 @@ input:focus { display: flex; margin-right: 12px; } - + .message-group { max-width: 90%; } - + .message-image-attachement { max-height: 20em; } - + #desktop-aside { display: none; }