Make generateMessage aware of temp messages, make sent attachements show on chat

This commit is contained in:
天クマ 2026-07-14 19:10:59 -03:00
commit fe86db2987
2 changed files with 123 additions and 63 deletions

84
app.js
View file

@ -39,7 +39,7 @@ function setupEventListeners() {
ui.toggleChatState(); ui.toggleChatState();
} }
}); });
elements.refreshChatsBtn.addEventListener('click', () => { elements.refreshChatsBtn.addEventListener('click', () => {
elements.refreshChatsBtn.classList.add('spinning'); elements.refreshChatsBtn.classList.add('spinning');
fetchChats().finally(() => { fetchChats().finally(() => {
@ -48,7 +48,7 @@ function setupEventListeners() {
}, 600); }, 600);
}); });
}); });
elements.chatSearch.addEventListener('input', (e) => { elements.chatSearch.addEventListener('input', (e) => {
const query = e.target.value.toLowerCase(); const query = e.target.value.toLowerCase();
const filtered = chatsState.filter(chat => const filtered = chatsState.filter(chat =>
@ -56,24 +56,24 @@ function setupEventListeners() {
); );
ui.renderChatList(filtered, activeChatState, selectChat); ui.renderChatList(filtered, activeChatState, selectChat);
}); });
elements.messageForm.addEventListener('submit', (e) => { elements.messageForm.addEventListener('submit', (e) => {
e.preventDefault(); e.preventDefault();
sendMessage(); sendMessage();
}); });
elements.chatBottomBar.style.height = `${elements.chatInputPanel.offsetHeight}px`; elements.chatBottomBar.style.height = `${elements.chatInputPanel.offsetHeight}px`;
const observer = new ResizeObserver(() => { const observer = new ResizeObserver(() => {
elements.chatBottomBar.style.height = elements.chatBottomBar.style.height =
`${elements.chatInputPanel.offsetHeight}px`; `${elements.chatInputPanel.offsetHeight}px`;
}); });
observer.observe(elements.chatInputPanel); observer.observe(elements.chatInputPanel);
elements.chatBottomBarBtn.addEventListener('click', ui.toggleChatBottomBar); elements.chatBottomBarBtn.addEventListener('click', ui.toggleChatBottomBar);
elements.chatBottomBar.addEventListener('click', (e) => { elements.chatBottomBar.addEventListener('click', (e) => {
if (e.target == e.currentTarget) ui.toggleChatBottomBar(); if (e.target == e.currentTarget) ui.toggleChatBottomBar();
}); });
elements.attachmentBtn.addEventListener('click', () => { elements.attachmentBtn.addEventListener('click', () => {
elements.attachmentInput.click(); elements.attachmentInput.click();
}) })
@ -81,12 +81,12 @@ function setupEventListeners() {
const firstFile = this.files[0]; const firstFile = this.files[0];
sendFileMessage(firstFile); sendFileMessage(firstFile);
}) })
elements.backToSidebarBtn.addEventListener('click', () => { elements.backToSidebarBtn.addEventListener('click', () => {
elements.sidebar.classList.remove('hidden'); elements.sidebar.classList.remove('hidden');
elements.activeChatContainer.classList.add('hidden'); elements.activeChatContainer.classList.add('hidden');
}); });
elements.settingsIconBtn.addEventListener('click', openSettings); elements.settingsIconBtn.addEventListener('click', openSettings);
elements.cancelSettingsBtn.addEventListener('click', () => ui.toggleModal(false)); elements.cancelSettingsBtn.addEventListener('click', () => ui.toggleModal(false));
elements.saveSettingsBtn.addEventListener('click', saveSettings); elements.saveSettingsBtn.addEventListener('click', saveSettings);
@ -112,18 +112,18 @@ function normalizeChatId(raw) {
function handleIncomingMessage(msg) { function handleIncomingMessage(msg) {
if (!msg) return; if (!msg) return;
// console.log('[WS] handleIncomingMessage payload:', msg); // console.log('[WS] handleIncomingMessage payload:', msg);
const rawChatId = msg.chatId || msg.from || (msg.chat && msg.chat.id); const rawChatId = msg.chatId || msg.from || (msg.chat && msg.chat.id);
const msgChatId = normalizeChatId(rawChatId); const msgChatId = normalizeChatId(rawChatId);
if (!msgChatId) { if (!msgChatId) {
console.warn('[WS] Could not resolve chatId from payload:', msg); console.warn('[WS] Could not resolve chatId from payload:', msg);
return; return;
} }
// console.log('[WS] Resolved msgChatId:', msgChatId, '| activeChatState:', activeChatState?.id); // console.log('[WS] Resolved msgChatId:', msgChatId, '| activeChatState:', activeChatState?.id);
if (activeChatState && activeChatState.id === msgChatId) { if (activeChatState && activeChatState.id === msgChatId) {
const msgId = normalizeChatId(msg.id) || msg.id; const msgId = normalizeChatId(msg.id) || msg.id;
const exists = document.getElementById(msgId); const exists = document.getElementById(msgId);
@ -132,23 +132,23 @@ function handleIncomingMessage(msg) {
ui.scrollToBottom(); ui.scrollToBottom();
} }
} }
const chatIndex = chatsState.findIndex(c => c.id === msgChatId); const chatIndex = chatsState.findIndex(c => c.id === msgChatId);
if (chatIndex !== -1) { if (chatIndex !== -1) {
const chat = chatsState[chatIndex]; const chat = chatsState[chatIndex];
chat.lastMessage = msg.body || msg.text || 'Media message'; chat.lastMessage = msg.body || msg.text || 'Media message';
chat.timestamp = msg.timestamp ? (msg.timestamp * 1000) : Date.now(); chat.timestamp = msg.timestamp ? (msg.timestamp * 1000) : Date.now();
if (!msg.fromMe && (!activeChatState || activeChatState.id !== msgChatId)) { if (!msg.fromMe && (!activeChatState || activeChatState.id !== msgChatId)) {
chat.unreadCount = (chat.unreadCount || 0) + 1; chat.unreadCount = (chat.unreadCount || 0) + 1;
} }
chatsState.sort((a, b) => { chatsState.sort((a, b) => {
const tA = new Date(a.timestamp).getTime(); const tA = new Date(a.timestamp).getTime();
const tB = new Date(b.timestamp).getTime(); const tB = new Date(b.timestamp).getTime();
return tB - tA; return tB - tA;
}); });
// ui.renderChatList(chatsState, activeChatState, selectChat); // ui.renderChatList(chatsState, activeChatState, selectChat);
} else { } else {
// fetchChats(); // fetchChats();
@ -182,13 +182,13 @@ async function selectChat(chat) {
activeChatState = chat; activeChatState = chat;
chat.unreadCount = 0; chat.unreadCount = 0;
// ui.renderChatList(chatsState, activeChatState, selectChat); // ui.renderChatList(chatsState, activeChatState, selectChat);
ui.toggleChatState(true); ui.toggleChatState(true);
elements.activeChatName.textContent = chat.name.toUpperCase(); elements.activeChatName.textContent = chat.name.toUpperCase();
elements.activeChatAvatar.textContent = chat.name ? chat.name.substring(0, 1).toUpperCase() : '?'; elements.activeChatAvatar.textContent = chat.name ? chat.name.substring(0, 1).toUpperCase() : '?';
elements.messagesContainer.innerHTML = ` elements.messagesContainer.innerHTML = `
<div class="loading-chats"> <div class="loading-chats">
<div class='dots'> <div class='dots'>
@ -205,7 +205,7 @@ async function selectChat(chat) {
if (window.innerWidth <= 768) { if (window.innerWidth <= 768) {
elements.sidebar.classList.add('hidden'); elements.sidebar.classList.add('hidden');
} }
try { try {
const rawMessages = await waha.getChatMessages(chat.id); const rawMessages = await waha.getChatMessages(chat.id);
const processedMessages = compensateMessageOrdering(rawMessages); const processedMessages = compensateMessageOrdering(rawMessages);
@ -219,9 +219,9 @@ async function selectChat(chat) {
async function sendMessage() { async function sendMessage() {
const text = elements.messageInput.value.trim(); const text = elements.messageInput.value.trim();
if (!text || !activeChatState) return; if (!text || !activeChatState) return;
elements.messageInput.value = ''; elements.messageInput.value = '';
const tempMsg = { const tempMsg = {
id: 'temp-' + Date.now(), id: 'temp-' + Date.now(),
body: text, body: text,
@ -230,10 +230,10 @@ async function sendMessage() {
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
status: 'sending' status: 'sending'
}; };
ui.appendSingleMessage(tempMsg, activeChatState.name, userInfo.id); ui.appendSingleMessage(tempMsg, activeChatState.name, userInfo.id);
ui.scrollToBottom(); ui.scrollToBottom();
try { try {
try { try {
await waha.startTyping(activeChatState.id); await waha.startTyping(activeChatState.id);
@ -242,13 +242,13 @@ async function sendMessage() {
} catch (e) { } catch (e) {
console.warn('Presence start failed:', e); console.warn('Presence start failed:', e);
} }
try { try {
await waha.stopTyping(activeChatState.id); await waha.stopTyping(activeChatState.id);
} catch (e) { } catch (e) {
console.warn('Presence stop failed:', e); console.warn('Presence stop failed:', e);
} }
try { try {
if (!activeChatState.id.endsWith('@lid')) { if (!activeChatState.id.endsWith('@lid')) {
await waha.readChat(activeChatState.id); await waha.readChat(activeChatState.id);
@ -256,9 +256,9 @@ async function sendMessage() {
} catch (e) { } catch (e) {
console.warn('readChat failed (non-fatal):', e.message); console.warn('readChat failed (non-fatal):', e.message);
} }
const responseData = await waha.sendTextMessage(activeChatState.id, text); const responseData = await waha.sendTextMessage(activeChatState.id, text);
const tempBubble = document.getElementById(tempMsg.id); const tempBubble = document.getElementById(tempMsg.id);
if (tempBubble) { if (tempBubble) {
if (responseData && responseData.id) { if (responseData && responseData.id) {
@ -267,7 +267,7 @@ async function sendMessage() {
const meta = tempBubble.querySelector('.message-meta'); const meta = tempBubble.querySelector('.message-meta');
meta.innerHTML = `<span>${formatTime(new Date())}</span><span style="width:14px; height:14px;" class="mif-done">`; meta.innerHTML = `<span>${formatTime(new Date())}</span><span style="width:14px; height:14px;" class="mif-done">`;
} }
activeChatState.lastMessage = text; activeChatState.lastMessage = text;
activeChatState.timestamp = new Date(); activeChatState.timestamp = new Date();
@ -289,9 +289,29 @@ async function sendMessage() {
async function sendFileMessage(file) { async function sendFileMessage(file) {
try { try {
const tempId = 'temp-' + Date.now();
const tempMsg = {
_data: {
mimetype: file.type
},
id: tempId,
body: "",
fromMe: true,
sender: 'me',
timestamp: new Date().toISOString(),
status: 'sending',
hasMedia: true,
media: {
url: URL.createObjectURL(file),
filename: file.name
}
};
ui.appendSingleMessage(tempMsg, activeChatState.name, userInfo.id, activeChatState.id, true);
ui.scrollToBottom();
const result = await waha.sendFileMessage(activeChatState.id, file); const result = await waha.sendFileMessage(activeChatState.id, file);
console.log(result) ui.updateMessageTick(tempId, result.status);
// ui.appendSingleMessage(result, activeChatState.name, userInfo.id, activeChatState.id);
} catch (error) { } catch (error) {
console.error(error.message); console.error(error.message);
} }

102
ui.js
View file

@ -142,11 +142,29 @@ export const ui = {
/** /**
* Append a single message (used for optimistic updates immediately upon sending) * Append a single message (used for optimistic updates immediately upon sending)
*/ */
appendSingleMessage(msg, activeChatName, userID, chatId) { appendSingleMessage(msg, activeChatName, userID, chatId, isLocal = false) {
elements.messagesContainer.appendChild(this.generateMessage(msg, activeChatName, userID, chatId)) elements.messagesContainer.appendChild(this.generateMessage(msg, activeChatName, userID, chatId, isLocal))
}, },
generateMessage(msg, activeChatName, userID, chatId) { 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 isOutgoing = msg.fromMe || msg.sender === 'me';
const groupDiv = document.createElement('div'); const groupDiv = document.createElement('div');
@ -187,43 +205,50 @@ export const ui = {
contentEl.appendChild(textEl); contentEl.appendChild(textEl);
bubble.appendChild(contentEl); bubble.appendChild(contentEl);
if (msg.hasMedia) { if (msg.hasMedia) {
const a = document.createElement('a'); let a;
a.innerText = `[Request media]`;
a.target = "_blank";
const clickListener = async (e) => { if (isLocal) {
a.removeEventListener('click', clickListener); a = this.generateTempMessageLink(msg);
a.innerText = `[Downloading]`; } else {
const mediaMsg = await waha.getSingleChatMessage(chatId, msg.id, true); a = document.createElement('a');
a.innerText = `[Request media]`;
a.target = "_blank";
const url = new URL(mediaMsg.media.url); const clickListener = async (e) => {
const reqID = url.pathname.split('/').filter(Boolean).pop(); a.removeEventListener('click', clickListener);
a.innerText = `[Downloading]`;
const { blob, filename } = await waha.downloadMedia(reqID); const mediaMsg = msg.media ? msg : await waha.getSingleChatMessage(chatId, msg.id, true);
console.log(mediaMsg.media.url);
const objectUrl = URL.createObjectURL(blob); const url = new URL(mediaMsg.media.url);
e.target.href = objectUrl; const reqID = url.pathname.split('/').filter(Boolean).pop();
if (blob.type.startsWith('image/')) { const { blob, filename } = await waha.downloadMedia(reqID);
a.textContent = "";
const img = document.createElement('img'); const objectUrl = URL.createObjectURL(blob);
img.classList.add('message-image-attachement'); e.target.href = objectUrl;
img.src = objectUrl;
a.appendChild(img); if (blob.type.startsWith('image/')) {
} else { a.textContent = "";
e.target.textContent = filename || `Download ${mediaMsg.media.filename}`; 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); contentEl.appendChild(a);
a.addEventListener('click', clickListener);
if (msg._data.mimetype.startsWith('image/')) { if (!isLocal && msg._data?.mimetype?.startsWith('image/')) {
a.click(); a.click();
} }
} }
const meta = document.createElement('div'); const meta = document.createElement('div');
meta.className = 'message-meta'; meta.className = 'message-meta';
@ -233,7 +258,7 @@ export const ui = {
let uid = ""; let uid = "";
function getPrevMessageElem() { function getPrevMessageElem() {
return elements.messagesContainer.lastElementChild; // < key change return elements.messagesContainer.lastElementChild;
} }
const prevMsgEl = getPrevMessageElem(); const prevMsgEl = getPrevMessageElem();
@ -266,6 +291,21 @@ export const ui = {
} }
}, },
updateMessageTick(id, status) {
let statusCheck;
if (status === 'read') {
statusCheck = '<span class="mif-done_all" style="color: var(--online-color); width:14px; height:14px;"></span>';
} else if (status === 'delivered') {
statusCheck = '<span class="mif-done" style="width:14px; height:14px;"></span>';
} else if (status === 'sending') {
statusCheck = '<span class="mif-earth" style="width:14px; height:14px;"></span>';
} else {
statusCheck = '<span class="mif-done" style="width:14px; height:14px;"></span>';
}
document.getElementById(id).querySelector('.message-meta').outerHTML = statusCheck;
},
toggleChatBottomBar() { toggleChatBottomBar() {
elements.chatBottomBar.classList.toggle("collapsed"); elements.chatBottomBar.classList.toggle("collapsed");
} }