diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/recipient-search.js | 15 |
1 files changed, 12 insertions, 3 deletions
diff --git a/src/recipient-search.js b/src/recipient-search.js index 5909078..4853c66 100644 --- a/src/recipient-search.js +++ b/src/recipient-search.js @@ -132,7 +132,8 @@ onCacheReady(loadSearchProfiles); try { const now = Date.now(); for (const [q, e] of Object.entries(JSON.parse(localStorage.getItem(QUERY_KEY) || '{}'))) - if (e && Array.isArray(e.rows) && now - (e.t || 0) < QUERY_TTL) { queryCache.set(q, e.rows); queryAt.set(q, e.t); } + // cached rows are final: their profile lookup already finished + if (e && Array.isArray(e.rows) && now - (e.t || 0) < QUERY_TTL) { queryCache.set(q, e.rows.map((r) => ({ ...r, known: true }))); queryAt.set(q, e.t); } } catch {} let persistTimer = null; function persistCaches() { @@ -199,11 +200,13 @@ export async function searchRecipients(qRaw, onPartial = null) { // Within a source, a name that actually contains the query outranks the // search relay's looser relevance matches. const score = (r) => ((r.name || '').toLowerCase().replace(/\s+/g, ' ').includes(q) || (r.address || r.nip05 || '').toLowerCase().includes(q)) ? 0 : 1; + // A row is `known` once its profile has been looked up (or arrived with the + // search itself): only then does a missing picture mean "has none". const snapshot = () => { const rows = [...out.values()].sort((a, b) => a.pri - b.pri || score(a) - score(b)).slice(0, MAX_RESULTS); for (const r of rows) { const p = profileCache.get(r.pk); - if (p) { r.picture ||= p.picture; r.name ||= p.name; } + if (p) { r.picture ||= p.picture; r.name ||= p.name; r.known = true; } } return rows; }; @@ -239,6 +242,7 @@ export async function searchRecipients(qRaw, onPartial = null) { const rows = snapshot(); await fillProfiles(rows).catch(() => {}); + for (const r of rows) r.known = true; // looked: no picture now means none if (queryCache.size > 200) { queryCache.clear(); queryAt.clear(); } queryCache.set(q, rows); queryAt.set(q, Date.now()); @@ -281,7 +285,12 @@ export function resultRows(h, rows, onPick, wrapAvatar) { // background, not <img>: paints synchronously from cache, so rows // re-rendered per keystroke don't flash their avatars ? h('div', { class: 'chat-avatar ava-img', style: `background-image:url(${JSON.stringify(r.picture)})` }) - : fallbackAvatar(h, r.pk, r.name, 'chat-avatar'); + // A registrar hit lands before its profile does. Punk art means "has + // no picture", never "picture not here yet" — a new account that + // picked another punk or uploaded a photo would flash the wrong face. + : !r.known + ? h('div', { class: 'chat-avatar fallback loading' }) + : fallbackAvatar(h, r.pk, r.name, 'chat-avatar'); return h('div', { class: 'item chat-thread-row', onClick: () => onPick(r) }, (wrapAvatar && wrapAvatar(r.pk, ava)) || ava, h('div', { class: 'col grow', style: 'min-width:0;gap:1px' }, |
