Add pagination to the email list (was hardcoded to newest 50, no way to see older mail)
listMessages() only ever fetched everything then kept the newest
limit (default 50) messages, with no offset parameter and no way for
the client to know how many messages existed beyond what it was
shown - older mail was completely unreachable through the webmail UI.
__imap::listMessages() now takes an offset (counted back from the
newest message) and returns {"total":N,"messages":[...]} instead of
a bare array, so the client can page backward/forward and know when
it has reached either end. __imaplist tag and /email/list both pass
offset through; /email/search is unchanged (search results are not
paginated). email.js tracks currentOffset (reset alongside
currentFolder/currentUid at every existing reset site), and renders
Prev/Next controls next to the unread count showing the current
1-based range and total.
This commit is contained in:
1 parent
dcfe2a667c
commit
41fbf0b7d9
5 files changed
+103
-19
No files matched your search
@@ -35,6 +35,14 @@
|
||||
'<path d="M9 7V4h6v3"/>' +
|
||||
'<path d="M6 7l1 13a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2l1-13"/>' +
|
||||
'<path d="M10 11v6M14 11v6"/>' +
|
||||
'</svg>',
|
||||
pagePrev:
|
||||
'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">' +
|
||||
'<path d="M15 18l-6-6 6-6"/>' +
|
||||
'</svg>',
|
||||
pageNext:
|
||||
'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">' +
|
||||
'<path d="M9 18l6-6-6-6"/>' +
|
||||
'</svg>'
|
||||
};
|
||||
|
||||
@@ -49,6 +57,19 @@
|
||||
// showing search results instead (see performSearch()/clearSearch()
|
||||
// and loadMessageList()'s own branch on this).
|
||||
var currentSearchQuery = null;
|
||||
// How many newest-first messages /email/list has already skipped -
|
||||
// 0 is the newest page. Must match LIST_LIMIT server-side (email.jet
|
||||
// defaults "limit" to 50 too) so a page is exactly one "limit"
|
||||
// worth of messages. Reset to 0 everywhere currentFolder/currentUid
|
||||
// already get reset (folder switch, search, account switch) - a
|
||||
// stale offset from one folder/account applied to another would
|
||||
// silently skip or duplicate messages.
|
||||
var currentOffset = 0;
|
||||
var LIST_LIMIT = 50;
|
||||
// Total message count the server reported for the CURRENT page's
|
||||
// folder/query, used only to enable/disable the Next button -
|
||||
// updated every time updateListCount() runs.
|
||||
var currentTotal = 0;
|
||||
// The array last handed to renderMessageList() - kept so openMessage()
|
||||
// can flip a single message's .seen flag locally and recompute the
|
||||
// toolbar's unread/total count without a round trip.
|
||||
@@ -70,6 +91,7 @@
|
||||
return;
|
||||
currentFolder = folder;
|
||||
currentUid = null;
|
||||
currentOffset = 0;
|
||||
// A folder click always means "show me this folder, plain" - drops
|
||||
// out of search mode even if the folder didn't actually change,
|
||||
// which is exactly how a user would expect to back out of a
|
||||
@@ -129,6 +151,7 @@
|
||||
'<div id="email-toolbar">' +
|
||||
'<span id="email-connected-as">' + escapeHtml(userEmail) + '</span>' +
|
||||
'<span id="email-list-count"></span>' +
|
||||
'<span id="email-pagination"></span>' +
|
||||
'<span id="email-toolbar-actions">' +
|
||||
'<span id="email-search-box">' +
|
||||
'<input type="text" id="email-search-input" placeholder="Search mail…">' +
|
||||
@@ -207,6 +230,7 @@
|
||||
var query = input.value.trim();
|
||||
currentSearchQuery = query === '' ? null : query;
|
||||
currentUid = null;
|
||||
currentOffset = 0;
|
||||
document.getElementById('email-reading-pane').innerHTML =
|
||||
'<div id="email-empty-state">Select a message to read it.</div>';
|
||||
loadMessageList();
|
||||
@@ -234,10 +258,51 @@
|
||||
countEl.textContent = '(' + unread + '/' + messages.length + ')';
|
||||
}
|
||||
|
||||
function renderMessageList(messages) {
|
||||
// total === null means "not paginated" (search results, or nothing
|
||||
// loaded yet) - hides the whole control rather than showing a
|
||||
// meaningless "1-0 of null". Otherwise renders the current page's
|
||||
// 1-based range and enables Prev/Next based on currentOffset/total,
|
||||
// matching how far a click on either button would actually move.
|
||||
function updatePaginationControls(total) {
|
||||
var el = document.getElementById('email-pagination');
|
||||
if (!el)
|
||||
return;
|
||||
currentTotal = total || 0;
|
||||
if (total === null || total === 0) {
|
||||
el.innerHTML = '';
|
||||
return;
|
||||
}
|
||||
var rangeStart = total === 0 ? 0 : currentOffset + 1;
|
||||
var rangeEnd = Math.min(currentOffset + LIST_LIMIT, total);
|
||||
var atStart = currentOffset <= 0;
|
||||
var atEnd = currentOffset + LIST_LIMIT >= total;
|
||||
el.innerHTML =
|
||||
'<button type="button" id="email-page-prev" class="icon-btn"' + (atStart ? ' disabled' : '') + ' title="Newer messages">' + ICONS.pagePrev + '</button>' +
|
||||
'<span id="email-page-range">' + rangeStart + '–' + rangeEnd + ' of ' + total + '</span>' +
|
||||
'<button type="button" id="email-page-next" class="icon-btn"' + (atEnd ? ' disabled' : '') + ' title="Older messages">' + ICONS.pageNext + '</button>';
|
||||
var prevBtn = document.getElementById('email-page-prev');
|
||||
var nextBtn = document.getElementById('email-page-next');
|
||||
if (prevBtn) prevBtn.addEventListener('click', function() {
|
||||
if (currentOffset <= 0)
|
||||
return;
|
||||
currentOffset = Math.max(0, currentOffset - LIST_LIMIT);
|
||||
currentUid = null;
|
||||
loadMessageList();
|
||||
});
|
||||
if (nextBtn) nextBtn.addEventListener('click', function() {
|
||||
if (currentOffset + LIST_LIMIT >= currentTotal)
|
||||
return;
|
||||
currentOffset += LIST_LIMIT;
|
||||
currentUid = null;
|
||||
loadMessageList();
|
||||
});
|
||||
}
|
||||
|
||||
function renderMessageList(messages, total) {
|
||||
var list = document.getElementById('email-list');
|
||||
currentMessages = messages;
|
||||
updateListCount(messages);
|
||||
updatePaginationControls(total === undefined ? null : total);
|
||||
if (!messages.length) {
|
||||
list.innerHTML = '<div id="email-empty-state">No messages.</div>';
|
||||
return;
|
||||
@@ -397,10 +462,11 @@
|
||||
// (see __imap.cpp's shared parseSummaryLines()), so this is the
|
||||
// only place that needs to know search mode even exists -
|
||||
// renderMessageList() below has no idea which one produced what
|
||||
// it's rendering.
|
||||
// it's rendering. Search isn't paginated (offset/limit only apply
|
||||
// to the plain listing) - a search result set is returned in full.
|
||||
var request = currentSearchQuery
|
||||
? post('/email/search', { folder: currentFolder, query: currentSearchQuery })
|
||||
: post('/email/list', { folder: currentFolder });
|
||||
: post('/email/list', { folder: currentFolder, offset: currentOffset, limit: LIST_LIMIT });
|
||||
return Promise.all([
|
||||
request,
|
||||
skipFade ? true : delay(180)
|
||||
@@ -409,8 +475,10 @@
|
||||
var res = results[0];
|
||||
if (!res.ok && res.error)
|
||||
list.innerHTML = '<div id="email-empty-state">' + escapeHtml(res.error) + '</div>';
|
||||
else if (currentSearchQuery)
|
||||
renderMessageList(Array.isArray(res) ? res : [], null);
|
||||
else
|
||||
renderMessageList(Array.isArray(res) ? res : []);
|
||||
renderMessageList(Array.isArray(res.messages) ? res.messages : [], res.total || 0);
|
||||
// Every place that reloads the list (connect, folder switch,
|
||||
// refresh, post-trash) is a place the unseen counts next to
|
||||
// each folder name could plausibly have changed too - one
|
||||
@@ -482,6 +550,7 @@
|
||||
function resetViewForActiveAccount(user) {
|
||||
currentFolder = 'Inbox';
|
||||
currentUid = null;
|
||||
currentOffset = 0;
|
||||
currentSearchQuery = null;
|
||||
var searchInput = document.getElementById('email-search-input');
|
||||
if (searchInput) searchInput.value = '';
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
<if value1="$[limit]" value2="" type="eq">
|
||||
<set name="limit" value="50">
|
||||
</if>
|
||||
<imaplist sessionid="" folder="$[folder]" limit="$[limit]">
|
||||
<imaplist sessionid="" folder="$[folder]" limit="$[limit]" offset="$[offset]">
|
||||
</imap>
|
||||
<catch name="_imaperr">
|
||||
{"ok":false,"error":"$[_imaperr]"}
|
||||
|
||||
Reference in new issue
Block a user