diff --git a/js/db.js b/js/db.js index de62c76..497e74f 100644 --- a/js/db.js +++ b/js/db.js @@ -30,6 +30,7 @@ function openDb() { msgStore = db.createObjectStore("messages", { keyPath: "id" }); msgStore.createIndex("from", "from", { unique: false }); msgStore.createIndex("fingerprint", ["from", "timestamp"], { unique: false }); + msgStore.createIndex("cidTimestampId", ["chatId", "timestamp", "id"], { unique: false }); } else { msgStore = tx.objectStore("messages"); } @@ -172,6 +173,39 @@ export async function loadLatestMessages(chatId, limit = 50) { }); } +export async function loadOlderMessages(chatId, oldestTimestamp, oldestId, limit = 50) { + const db = await openDb(); + + return new Promise((resolve, reject) => { + const tx = db.transaction("messages", "readonly"); + const store = tx.objectStore("messages"); + const idx = store.index("cidTimestampId"); + + const out = []; + + const parsedTimestamp = isNaN(oldestTimestamp) ? oldestTimestamp : Number(oldestTimestamp); + + const range = IDBKeyRange.bound( + [chatId, -Infinity, ""], + [chatId, parsedTimestamp, oldestId], + false, + false + ); + + idx.openCursor(range, "prev").onsuccess = (e) => { + const cursor = e.target.result; + if (!cursor) return resolve(out); + + out.push(cursor.value); + + if (out.length >= limit) resolve(out); + else cursor.continue(); + }; + + tx.onerror = () => reject(tx.error); + }); +} + export async function upsertMedia(reqId, blob, filename) { const db = await openDb(); diff --git a/js/storage.js b/js/storage.js index 1159816..f83d66c 100644 --- a/js/storage.js +++ b/js/storage.js @@ -1,4 +1,4 @@ -import { loadChatsSorted, loadLatestMessages, loadMedia, upsertChats, upsertMedia, upsertMessages } from "./db.js"; +import { loadChatsSorted, loadLatestMessages, loadMedia, loadOlderMessages, upsertChats, upsertMedia, upsertMessages } from "./db.js"; import { waha } from "./waha.js"; let online = false; @@ -107,6 +107,14 @@ export async function getChatMessages(chatId) { } } +export async function getMoreChatMessages(chatId, oldestTimestamp, oldestId, limit = 50) { + if (online) { + return waha.getChatMessages(chatId, oldestTimestamp); + } else { + return await loadOlderMessages(chatId, oldestTimestamp, oldestId); + } +} + export async function getChatPicture(chatId) { if (online) { return await waha.getChatPicture(chatId); diff --git a/js/ui.js b/js/ui.js index cfe586d..3642831 100644 --- a/js/ui.js +++ b/js/ui.js @@ -1,7 +1,7 @@ import { formatTime } from "./utils.js"; import { waha } from "./waha.js"; import { config } from "./config.js"; -import { getChatPicture, getMessage, getMedia } from "./storage.js"; +import { getChatPicture, getMessage, getMedia, getMoreChatMessages } from "./storage.js"; export const elements = { chatList: document.getElementById('chat-list'), @@ -136,19 +136,48 @@ 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"; + loadMore.addEventListener('click', () => { + this.loadMoreMessages(chatId, userID); + }); + elements.messagesContainer.appendChild(loadMore); for (const msg of messages) { - this.appendSingleMessage(msg, activeChatName, userID, chatId); + this.appendSingleMessage(msg, userID, chatId); } this.scrollToBottom(); }, + + async loadMoreMessages(chatId, userId) { + const oldest = document.querySelector('.message-group:first-of-type'); + const oldestTimestamp = oldest.dataset.timestamp; + const oldestId = oldest.id; + 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); + }, /** * 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)) + appendSingleMessage(msg, userID, chatId, isLocal = false) { + elements.messagesContainer.appendChild(this.generateMessage(msg, userID, chatId, isLocal)) }, generateTempMessageLink(msg) { @@ -169,7 +198,7 @@ export const ui = { return a; }, - generateMessage(msg, activeChatName, userID, chatId, isLocal = false) { + generateMessage(msg, userID, chatId, isLocal = false) { const isOutgoing = msg.fromMe || msg.sender === 'me'; function getPrevMessageElem() { @@ -181,6 +210,7 @@ export const ui = { const groupDiv = document.createElement('div'); groupDiv.className = `message-group ${isOutgoing ? 'outgoing' : 'incoming'}`; groupDiv.id = msg.id; + groupDiv.dataset.timestamp = msg.timestamp; groupDiv.dataset.from = msg.participant || msg.from; const senderName = isOutgoing ? userID : (msg._data.notifyName || msg.from); diff --git a/js/waha.js b/js/waha.js index 3c3050f..66683b1 100644 --- a/js/waha.js +++ b/js/waha.js @@ -69,7 +69,7 @@ async function downloadFile(path, options = {}) { export const waha = { async getVersion() { - return request('/api/version'); + return await request('/api/version'); }, async getChats() { @@ -89,8 +89,9 @@ export const waha = { }); }, - async getChatMessages(chatId) { - return request(`/api/${config.session}/chats/${chatId}/messages?downloadMedia=false&limit=40`); + async getChatMessages(chatId, beforeTimestamp) { + console.log(`/api/${config.session}/chats/${chatId}/messages?downloadMedia=false&limit=40&sortBy=timestamp${beforeTimestamp ? `&filter.timestamp.gte=${beforeTimestamp}` : "" }`); + return request(`/api/${config.session}/chats/${chatId}/messages?downloadMedia=false&limit=40${beforeTimestamp ? `&filter.timestamp.lte=${beforeTimestamp}` : "" }`); }, async getSingleChatMessage(chatId, messageId, downladMedia) { diff --git a/style.css b/style.css index bea420c..3ea409c 100644 --- a/style.css +++ b/style.css @@ -506,6 +506,18 @@ input:focus { radial-gradient(circle at 90% 80%, rgba(168, 85, 247, 0.03) 0%, transparent 40%); } +.load-more-btn { + margin: auto; + background-color: var(--bg-secondary); + padding: .4em; + cursor: pointer; +} + +.load-more-btn:hover { + background-color: var(--text-primary); + color: var(--bg-main); +} + .message-group { display: flex; flex-direction: column;