Mail: a button per account with its own unread count (kept fresh), instead of a dropdown
build-container / build (push) Successful in 14m15s

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5.5 committed 2026-10-23 10:31:58 -04:00
1 parent 7198efaccd
commit e984f11a94
3 files changed
+22 -8

No files matched your search

+10 -7
View File
@@ -38,7 +38,7 @@ const mailHasRemote = html => /<img[^>]+src=["']?https?:|url\(\s*["']?https?:/i.
// ---- unread badge and Home card
async function mailPoll() {
if (!(allowed('mail') && cfg.mail)) return;
try { const s = await jfetch('/api/mail/status'); mail.st = s; mail.accts = s.accounts || mail.accts; const n = s.connected && s.unread ? s.unread : 0; if (n !== mailUnread) { mailUnread = n; renderNav(); } } catch {}
try { const s = await jfetch('/api/mail/status'); mail.st = s; mail.accts = s.accounts || mail.accts; const n = s.connected && s.unread ? s.unread : 0; if (n !== mailUnread) { mailUnread = n; renderNav(); } if (location.hash.startsWith('#/mail') && typeof mailDrawChips === 'function') mailDrawChips(); } catch {}
}
async function renderMailCard(el) {
if (!el) return;
@@ -180,12 +180,16 @@ async function viewMail(args = []) {
jfetch('/api/mail/folders?acct=' + encodeURIComponent(acct)).then(f => { if (mail.acct !== acct) return; mail.folders = f; mcPut('f:' + acct, { folders: f }); mailRefreshFolders(); }).catch(() => {});
mailPoll().then(() => { if (mail.st && location.hash.startsWith('#/mail')) mailRefreshFolders(); });
}
/** One button per account with its own unread count, so you can see which one has new mail without switching. */
const mailChips = () => `<div class="maccts" role="tablist" aria-label="Accounts">${mail.accts.map(a => `<button type="button" role="tab" data-a="${esc(a.id)}" aria-selected="${a.id === mail.acct}" class="${a.id === mail.acct ? 'on' : ''}${a.unread ? ' new' : ''}"><span class="mal">${esc(a.name || a.address)}</span><small>${esc(a.name ? a.address : '')}</small>${a.unread ? `<i class="nbadge" style="position:static">${a.unread > 99 ? '99+' : a.unread}</i>` : ''}</button>`).join('')}</div>`;
function mailBindChips() { document.querySelectorAll('.maccts button').forEach(b => b.onclick = () => { if (b.dataset.a === mail.acct) return; mail.q = ''; location.hash = '#/mail/f/' + encodeURIComponent(b.dataset.a + '~inbox'); }); }
function mailDrawChips() { if (mail.accts.length < 2) return; document.querySelectorAll('.maccts').forEach(el => el.outerHTML = mailChips()); mailBindChips(); }
/** Redraw the folder pickers (names and unread counts) without touching the list or the open message. */
function mailRefreshFolders() {
mailDrawChips();
const fl = mail.folders; if (!fl.length) return;
const f = $('#mfsel'); if (f) f.innerHTML = fl.map(x => `<option value="${esc(x.id)}" ${x.id === mail.folder ? 'selected' : ''}>${esc(x.name)}${x.unread ? ' (' + x.unread + ')' : ''}</option>`).join('');
const side = document.querySelector('.mfold'); if (side) { const sel = side.querySelector('select'); side.innerHTML = (sel ? sel.outerHTML : '') + fl.map(x => `<a href="#/mail/f/${encodeURIComponent(x.id)}" class="${x.id === mail.folder ? 'on' : ''}"><span class="grow">${esc(x.name)}</span>${x.unread ? `<i class="nbadge" style="position:static">${x.unread}</i>` : ''}</a>`).join(''); const s2 = side.querySelector('select'); if (s2) s2.onchange = e => { mail.q = ''; location.hash = '#/mail/f/' + encodeURIComponent(e.target.value + '~inbox'); }; }
const acc = $('#macct'); if (acc && mail.accts.length) acc.innerHTML = mail.accts.map(a => `<option value="${esc(a.id)}" ${a.id === mail.acct ? 'selected' : ''}>${esc(a.address)}${a.unread ? ' (' + a.unread + ')' : ''}</option>`).join('');
const side = document.querySelector('.mfold'); if (side) { const sel = side.querySelector('select'); side.innerHTML = (mail.accts.length > 1 ? mailChips() : '') + fl.map(x => `<a href="#/mail/f/${encodeURIComponent(x.id)}" class="${x.id === mail.folder ? 'on' : ''}"><span class="grow">${esc(x.name)}</span>${x.unread ? `<i class="nbadge" style="position:static">${x.unread}</i>` : ''}</a>`).join(''); mailBindChips(); }
}
function mailConnectScreen(st) {
@@ -204,9 +208,9 @@ function mailConnectScreen(st) {
function mailShell(mode) {
const fl = mail.folders.length ? mail.folders : [{ id: 'inbox', name: 'Inbox', unread: mailUnread }];
$('#view').innerHTML = `<div class="mailapp" data-mode="${mode}">
<aside class="mfold" aria-label="Folders">${mail.accts.length > 1 ? `<select id="macct2" aria-label="Account" style="margin-bottom:8px">${mail.accts.map(a => `<option value="${esc(a.id)}" ${a.id === mail.acct ? 'selected' : ''}>${esc(a.address)}</option>`).join('')}</select>` : ''}${fl.map(f => `<a href="#/mail/f/${encodeURIComponent(f.id)}" class="${f.id === mail.folder ? 'on' : ''}"><span class="grow">${esc(f.name)}</span>${f.unread ? `<i class="nbadge" style="position:static">${f.unread}</i>` : ''}</a>`).join('')}</aside>
<aside class="mfold" aria-label="Folders">${mail.accts.length > 1 ? mailChips() : ''}${fl.map(f => `<a href="#/mail/f/${encodeURIComponent(f.id)}" class="${f.id === mail.folder ? 'on' : ''}"><span class="grow">${esc(f.name)}</span>${f.unread ? `<i class="nbadge" style="position:static">${f.unread}</i>` : ''}</a>`).join('')}</aside>
<section class="mlist" aria-label="Messages">
${mail.accts.length > 1 ? `<div class="mtool" style="margin-bottom:4px"><select id="macct" aria-label="Account" style="max-width:100%;flex:1">${mail.accts.map(a => `<option value="${esc(a.id)}" ${a.id === mail.acct ? 'selected' : ''}>${esc(a.address)}${a.unread ? ' (' + a.unread + ')' : ''}</option>`).join('')}</select></div>` : ''}
${mail.accts.length > 1 ? mailChips() : ''}
<div class="mtool"><select id="mfsel" aria-label="Folder">${fl.map(f => `<option value="${esc(f.id)}" ${f.id === mail.folder ? 'selected' : ''}>${esc(f.name)}${f.unread ? ' (' + f.unread + ')' : ''}</option>`).join('')}</select>
<input type="search" id="mq" placeholder="Search mail" value="${esc(mail.q)}" autocomplete="off" aria-label="Search mail">
<button class="ibtn" id="mrf" aria-label="Refresh">${icon('refresh')}</button><button class="btn" id="mnew">${icon('edit')}New</button></div>
@@ -214,8 +218,7 @@ function mailShell(mode) {
</section>
<section class="mread" id="mread" aria-label="Message"></section></div>`;
$('#mfsel').onchange = e => { mail.q = ''; location.hash = '#/mail/f/' + encodeURIComponent(e.target.value); };
if ($('#macct2')) $('#macct2').onchange = e => { mail.q = ''; location.hash = '#/mail/f/' + encodeURIComponent(e.target.value + '~inbox'); };
if ($('#macct')) $('#macct').onchange = e => { mail.q = ''; location.hash = '#/mail/f/' + encodeURIComponent(e.target.value + '~inbox'); };
mailBindChips();
$('#mnew').onclick = () => mailCompose();
$('#mrf').onclick = () => { mailLoadList(true); jfetch('/api/mail/folders?acct=' + encodeURIComponent(mail.acct)).then(f => { mail.folders = f; mailPoll(); }).catch(() => {}); };
let t; $('#mq').oninput = e => { clearTimeout(t); t = setTimeout(() => { mail.q = e.target.value.trim(); mailLoadList(true); }, 400); };