Make generateMessage aware of temp messages, make sent attachements show on chat
This commit is contained in:
parent
e388e185ba
commit
fe86db2987
2 changed files with 123 additions and 63 deletions
84
app.js
84
app.js
|
|
@ -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
102
ui.js
|
|
@ -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");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue