Add pagination to message screen.

This commit is contained in:
天クマ 2026-07-16 21:26:32 -03:00
commit cedbd2b3c0
5 changed files with 94 additions and 9 deletions

View file

@ -30,6 +30,7 @@ function openDb() {
msgStore = db.createObjectStore("messages", { keyPath: "id" }); msgStore = db.createObjectStore("messages", { keyPath: "id" });
msgStore.createIndex("from", "from", { unique: false }); msgStore.createIndex("from", "from", { unique: false });
msgStore.createIndex("fingerprint", ["from", "timestamp"], { unique: false }); msgStore.createIndex("fingerprint", ["from", "timestamp"], { unique: false });
msgStore.createIndex("cidTimestampId", ["chatId", "timestamp", "id"], { unique: false });
} else { } else {
msgStore = tx.objectStore("messages"); 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) { export async function upsertMedia(reqId, blob, filename) {
const db = await openDb(); const db = await openDb();

View file

@ -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"; import { waha } from "./waha.js";
let online = false; 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) { export async function getChatPicture(chatId) {
if (online) { if (online) {
return await waha.getChatPicture(chatId); return await waha.getChatPicture(chatId);

View file

@ -1,7 +1,7 @@
import { formatTime } from "./utils.js"; import { formatTime } from "./utils.js";
import { waha } from "./waha.js"; import { waha } from "./waha.js";
import { config } from "./config.js"; import { config } from "./config.js";
import { getChatPicture, getMessage, getMedia } from "./storage.js"; import { getChatPicture, getMessage, getMedia, getMoreChatMessages } from "./storage.js";
export const elements = { export const elements = {
chatList: document.getElementById('chat-list'), chatList: document.getElementById('chat-list'),
@ -137,18 +137,47 @@ export const ui = {
return; 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) { for (const msg of messages) {
this.appendSingleMessage(msg, activeChatName, userID, chatId); this.appendSingleMessage(msg, userID, chatId);
} }
this.scrollToBottom(); 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) * Append a single message (used for optimistic updates immediately upon sending)
*/ */
appendSingleMessage(msg, activeChatName, userID, chatId, isLocal = false) { appendSingleMessage(msg, userID, chatId, isLocal = false) {
elements.messagesContainer.appendChild(this.generateMessage(msg, activeChatName, userID, chatId, isLocal)) elements.messagesContainer.appendChild(this.generateMessage(msg, userID, chatId, isLocal))
}, },
generateTempMessageLink(msg) { generateTempMessageLink(msg) {
@ -169,7 +198,7 @@ export const ui = {
return a; return a;
}, },
generateMessage(msg, activeChatName, userID, chatId, isLocal = false) { generateMessage(msg, userID, chatId, isLocal = false) {
const isOutgoing = msg.fromMe || msg.sender === 'me'; const isOutgoing = msg.fromMe || msg.sender === 'me';
function getPrevMessageElem() { function getPrevMessageElem() {
@ -181,6 +210,7 @@ export const ui = {
const groupDiv = document.createElement('div'); const groupDiv = document.createElement('div');
groupDiv.className = `message-group ${isOutgoing ? 'outgoing' : 'incoming'}`; groupDiv.className = `message-group ${isOutgoing ? 'outgoing' : 'incoming'}`;
groupDiv.id = msg.id; groupDiv.id = msg.id;
groupDiv.dataset.timestamp = msg.timestamp;
groupDiv.dataset.from = msg.participant || msg.from; groupDiv.dataset.from = msg.participant || msg.from;
const senderName = isOutgoing ? userID : (msg._data.notifyName || msg.from); const senderName = isOutgoing ? userID : (msg._data.notifyName || msg.from);

View file

@ -69,7 +69,7 @@ async function downloadFile(path, options = {}) {
export const waha = { export const waha = {
async getVersion() { async getVersion() {
return request('/api/version'); return await request('/api/version');
}, },
async getChats() { async getChats() {
@ -89,8 +89,9 @@ export const waha = {
}); });
}, },
async getChatMessages(chatId) { async getChatMessages(chatId, beforeTimestamp) {
return request(`/api/${config.session}/chats/${chatId}/messages?downloadMedia=false&limit=40`); 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) { async getSingleChatMessage(chatId, messageId, downladMedia) {

View file

@ -506,6 +506,18 @@ input:focus {
radial-gradient(circle at 90% 80%, rgba(168, 85, 247, 0.03) 0%, transparent 40%); 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 { .message-group {
display: flex; display: flex;
flex-direction: column; flex-direction: column;