mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-17 10:15:27 -04:00
Email library chip-bar: smaller pills, persist across refresh, Esc + sender click
Four fixes from the first round of usage: 1. Pill height was larger than the chip-bar's row — shrink to a fixed 18px-tall pill (line-height + height pinned) so it sits inside the input row. 2. List refresh wiped pill state — when _loadEmails replaces state._libEmails (refresh, folder switch, etc.), refresh the snapshot to the new list and re-apply the pill filter so pills persist instead of resetting to 'show all emails'. 3. Click-to-add only worked inside the open email reader. Extend the capture-phase handler to ALSO catch clicks on .email-meta-sender inside the library grid — the list card's sender name is the most natural place to want to pivot from. 4. Esc inside the chip-input didn't close the modal. New behaviour: if the autocomplete dropdown is open, Esc closes only the dropdown (and swallows the event); otherwise Esc blurs the input and bubbles so the existing modal Esc handler can close the library. Also wires data-email + data-name on .email-meta-sender so the click handler has reliable targeting.
This commit is contained in:
+51
-10
@@ -1802,9 +1802,9 @@ function _renderSearchPills() {
|
|||||||
const esc = s => String(s || '').replace(/&/g, '&').replace(/</g, '<').replace(/"/g, '"');
|
const esc = s => String(s || '').replace(/&/g, '&').replace(/</g, '<').replace(/"/g, '"');
|
||||||
wrap.innerHTML = pills.map((p, i) => {
|
wrap.innerHTML = pills.map((p, i) => {
|
||||||
const label = p.type === 'contact' ? (p.name || p.email || '?') : (p.text || '');
|
const label = p.type === 'contact' ? (p.name || p.email || '?') : (p.text || '');
|
||||||
return `<span class="email-lib-pill" data-pill-idx="${i}" style="display:inline-flex;align-items:center;gap:4px;padding:2px 6px 2px 8px;border-radius:999px;background:color-mix(in srgb, var(--accent, var(--red)) 14%, transparent);color:var(--accent, var(--red));font-size:11px;line-height:1.4;font-weight:600;max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">
|
return `<span class="email-lib-pill" data-pill-idx="${i}" style="display:inline-flex;align-items:center;gap:2px;padding:0 4px 0 6px;border-radius:999px;background:color-mix(in srgb, var(--accent, var(--red)) 14%, transparent);color:var(--accent, var(--red));font-size:10px;line-height:18px;height:18px;font-weight:600;max-width:160px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex-shrink:0;">
|
||||||
<span style="overflow:hidden;text-overflow:ellipsis;">${esc(label)}</span>
|
<span style="overflow:hidden;text-overflow:ellipsis;">${esc(label)}</span>
|
||||||
<button type="button" class="email-lib-pill-x" data-pill-idx="${i}" title="Remove" style="background:transparent;border:0;color:inherit;cursor:pointer;font-size:13px;line-height:1;padding:0 2px;opacity:0.7;">×</button>
|
<button type="button" class="email-lib-pill-x" data-pill-idx="${i}" title="Remove" style="background:transparent;border:0;color:inherit;cursor:pointer;font-size:11px;line-height:1;padding:0 2px;opacity:0.7;">×</button>
|
||||||
</span>`;
|
</span>`;
|
||||||
}).join('');
|
}).join('');
|
||||||
wrap.querySelectorAll('.email-lib-pill-x').forEach(btn => {
|
wrap.querySelectorAll('.email-lib-pill-x').forEach(btn => {
|
||||||
@@ -1962,27 +1962,51 @@ async function _initEmailSearchChipBar() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (e.key === 'Escape') {
|
if (e.key === 'Escape') {
|
||||||
|
if (menuOpen) {
|
||||||
|
// Just close the dropdown — let the modal Esc handler run on the
|
||||||
|
// next Esc to actually dismiss the library.
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
_hideSearchSuggestions();
|
_hideSearchSuggestions();
|
||||||
|
} else {
|
||||||
|
// Blur first so the modal Esc handler doesn't get suppressed by
|
||||||
|
// any IME / typing-target check, and let the event propagate.
|
||||||
|
try { input.blur(); } catch (_) {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Click-to-add: if a recipient chip in the email reader is clicked, drop
|
// Click-to-add: clicking a recipient-chip in the email reader OR a
|
||||||
// the person into the library search as a contact pill so the user can
|
// .email-meta-sender in the library list drops the person into the
|
||||||
// pivot to "everything from / to this person" in one tap.
|
// library search as a contact pill so the user can pivot to "everything
|
||||||
|
// from / to this person" in one tap.
|
||||||
window.addEventListener('click', (e) => {
|
window.addEventListener('click', (e) => {
|
||||||
|
const lib = document.getElementById('email-lib-modal');
|
||||||
|
// 1) Recipient chips inside the email reader area
|
||||||
const chip = e.target.closest && e.target.closest('.recipient-chip');
|
const chip = e.target.closest && e.target.closest('.recipient-chip');
|
||||||
if (!chip) return;
|
if (chip && chip.closest('.email-reader-header, .email-card-reader, .email-reader-tab-modal')) {
|
||||||
// Only hijack inside the email reader area; ignore composer / forms etc.
|
|
||||||
if (!chip.closest('.email-reader-header, .email-card-reader, .email-reader-tab-modal')) return;
|
|
||||||
const email = (chip.dataset && chip.dataset.email) || '';
|
const email = (chip.dataset && chip.dataset.email) || '';
|
||||||
const name = (chip.dataset && chip.dataset.name) || (chip.textContent || '').trim();
|
const name = (chip.dataset && chip.dataset.name) || (chip.textContent || '').trim();
|
||||||
if (!email) return;
|
if (!email) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
// Surface the library window if it's hidden, then drop a pill in.
|
|
||||||
try { window.openEmailLibrary && window.openEmailLibrary(); } catch (_) {}
|
try { window.openEmailLibrary && window.openEmailLibrary(); } catch (_) {}
|
||||||
_addSearchPill({ type: 'contact', name, email });
|
_addSearchPill({ type: 'contact', name, email });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 2) Sender name in a library list card row (only when the library is open)
|
||||||
|
if (lib && !lib.classList.contains('hidden')) {
|
||||||
|
const senderEl = e.target.closest && e.target.closest('.email-meta-sender');
|
||||||
|
if (senderEl && senderEl.closest('#email-lib-grid')) {
|
||||||
|
const email = (senderEl.dataset && senderEl.dataset.email) || '';
|
||||||
|
const name = (senderEl.dataset && senderEl.dataset.name) || (senderEl.textContent || '').trim();
|
||||||
|
if (!email) return;
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
_addSearchPill({ type: 'contact', name, email });
|
||||||
|
}
|
||||||
|
}
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
async function _doSearch() {
|
async function _doSearch() {
|
||||||
@@ -2258,7 +2282,18 @@ async function _loadEmails({ force = false, useCache = true } = {}) {
|
|||||||
state._libEmails = data.emails || [];
|
state._libEmails = data.emails || [];
|
||||||
state._libTotal = data.total || 0;
|
state._libTotal = data.total || 0;
|
||||||
if (sp) sp.destroy();
|
if (sp) sp.destroy();
|
||||||
|
// If chip-bar pills are active, swap the snapshot to the freshly
|
||||||
|
// loaded list and re-apply the filter so pills persist across
|
||||||
|
// refreshes / folder switches instead of getting wiped.
|
||||||
|
const _activePills = (state._libSearchPills || []).length > 0
|
||||||
|
|| (state._libSearchDraft || '').length > 0;
|
||||||
|
if (_activePills) {
|
||||||
|
_libPreSearchEmails = state._libEmails.slice();
|
||||||
|
_libPreSearchTotal = state._libTotal;
|
||||||
|
_applyPillFilter();
|
||||||
|
} else {
|
||||||
_renderGrid();
|
_renderGrid();
|
||||||
|
}
|
||||||
const stats = document.getElementById('email-lib-stats');
|
const stats = document.getElementById('email-lib-stats');
|
||||||
if (stats) stats.textContent = `${state._libTotal} emails`;
|
if (stats) stats.textContent = `${state._libTotal} emails`;
|
||||||
_refreshUnreadBadge();
|
_refreshUnreadBadge();
|
||||||
@@ -2453,10 +2488,16 @@ function _createCard(em) {
|
|||||||
// hides the actually useful info. Outside Sent, show the sender as before.
|
// hides the actually useful info. Outside Sent, show the sender as before.
|
||||||
const isSentFolderEarly = /sent/i.test(state._libFolder);
|
const isSentFolderEarly = /sent/i.test(state._libFolder);
|
||||||
let senderName;
|
let senderName;
|
||||||
|
let senderAddress;
|
||||||
if (isSentFolderEarly) {
|
if (isSentFolderEarly) {
|
||||||
senderName = _formatRecipients(em.to) || em.to || '(no recipient)';
|
senderName = _formatRecipients(em.to) || em.to || '(no recipient)';
|
||||||
|
// First address out of em.to for click-to-pill targeting.
|
||||||
|
const _firstTo = String(em.to || '').split(',')[0] || '';
|
||||||
|
const _m = _firstTo.match(/<([^>]+)>/);
|
||||||
|
senderAddress = (_m ? _m[1] : _firstTo).trim();
|
||||||
} else {
|
} else {
|
||||||
senderName = em.from_name || em.from_address;
|
senderName = em.from_name || em.from_address;
|
||||||
|
senderAddress = em.from_address || '';
|
||||||
}
|
}
|
||||||
const color = _senderColor(senderName);
|
const color = _senderColor(senderName);
|
||||||
|
|
||||||
@@ -2588,7 +2629,7 @@ function _createCard(em) {
|
|||||||
meta.className = 'memory-item-meta';
|
meta.className = 'memory-item-meta';
|
||||||
meta.style.cssText = 'font-size:10px;opacity:0.7;margin-top:2px;';
|
meta.style.cssText = 'font-size:10px;opacity:0.7;margin-top:2px;';
|
||||||
const senderPrefix = isSentFolderEarly ? 'to ' : '';
|
const senderPrefix = isSentFolderEarly ? 'to ' : '';
|
||||||
meta.innerHTML = `<span class="email-meta-sender"><span style="opacity:0.55">${senderPrefix}</span><span style="color:${color};font-weight:600">${_esc(senderName)}</span></span><span class="email-meta-sep"> · </span><span class="email-meta-date">${_esc(dateStr)}</span>`;
|
meta.innerHTML = `<span class="email-meta-sender" data-email="${_esc(senderAddress || '')}" data-name="${_esc(senderName || '')}"><span style="opacity:0.55">${senderPrefix}</span><span style="color:${color};font-weight:600">${_esc(senderName)}</span></span><span class="email-meta-sep"> · </span><span class="email-meta-date">${_esc(dateStr)}</span>`;
|
||||||
content.appendChild(meta);
|
content.appendChild(meta);
|
||||||
|
|
||||||
card.appendChild(content);
|
card.appendChild(content);
|
||||||
|
|||||||
Reference in New Issue
Block a user