From e253cbd16d502cd382a6f7632d2851db4ae1a6ae Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Sat, 10 Oct 2026 10:28:12 -0700 Subject: Notifications: a custom-emoji reaction shows its picture. djmeistro's :peepoClap: read as the bare text "reacted :peepoClap:" — the notification row kept the reaction's content but not its NIP-30 emoji tag (and cut shortcodes at 12 characters). Rows now keep the tag's picture and the whole :name:, draw it like the reaction chips do, and a row saved the old way gets its picture when the relay hands the reaction again. tools/notif-custom-emoji-test.js. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Vps2H27rhbG9ZqseGfMrc3 --- src/features/messages.js | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) (limited to 'src/features') diff --git a/src/features/messages.js b/src/features/messages.js index 0a14b9c..cedd8db 100644 --- a/src/features/messages.js +++ b/src/features/messages.js @@ -13706,8 +13706,11 @@ export function messagesFeature(ctx) { if (my.includes(ev.pubkey)) return null; if (ev.kind === 7) { if (!lastE) return null; - const emoji = !ev.content || ev.content === '+' ? '\u2764\ufe0f' : ev.content.slice(0, 12); - return { id: ev.id, what: 'react', actor: ev.pubkey, target: lastE, hint, emoji, ts: ev.created_at }; + // a :shortcode: keeps its whole name and the picture its NIP-30 tag names + const code = shortcodeOf(ev.content || ''); + const tag = code && (ev.tags || []).find((x) => x[0] === 'emoji' && x[1] === code && /^https?:\/\//.test(x[2] || '')); + const emoji = !ev.content || ev.content === '+' ? '\u2764\ufe0f' : code ? ev.content.slice(0, 64) : ev.content.slice(0, 12); + return { id: ev.id, what: 'react', actor: ev.pubkey, target: lastE, hint, emoji, emojiUrl: tag ? tag[2] : undefined, ts: ev.created_at }; } if (ev.kind === 6) return lastE ? { id: ev.id, what: 'boost', actor: ev.pubkey, target: lastE, hint, ts: ev.created_at } : null; if (ev.kind === COMMENT) { @@ -13727,9 +13730,16 @@ export function messagesFeature(ctx) { const c = notifNow(); const known = new Set(c.items.map((x) => x.id)); for (const ev of evs) noteForSpam(ev); - const add = evs.map((ev) => ((ev.kind === 1 || ev.kind === COMMENT) && hidden(ev) ? null : notifItem(ev))) - .filter((x) => x && !hiddenPk(x.actor) && !known.has(x.id) && known.add(x.id)); - if (!add.length) return false; + const items = evs.map((ev) => ((ev.kind === 1 || ev.kind === COMMENT) && hidden(ev) ? null : notifItem(ev))); + // a custom-emoji reaction saved before rows kept its picture gets it now + let healed = false; + for (const x of items) { + if (!x || !x.emojiUrl || !known.has(x.id)) continue; + const old = c.items.find((y) => y.id === x.id); + if (old && !old.emojiUrl) { old.emojiUrl = x.emojiUrl; old.emoji = x.emoji; healed = true; } + } + const add = items.filter((x) => x && !hiddenPk(x.actor) && !known.has(x.id) && known.add(x.id)); + if (!add.length) { if (healed) persistNotifs(); return healed; } warmNotifFaces(add); // One zap, two receipts: a coinos zap publishes its own (9737) beside the // LNURL server's (9735). The same person, post and amount within a few @@ -13822,7 +13832,13 @@ export function messagesFeature(ctx) { } function notifLabel(x) { if (x.what === 'zap') return t('alertZap', { sats: fmtSats(x.sats) + ' sats' }); - if (x.what === 'react') return t('alertReact', { emoji: x.emoji }); + if (x.what === 'react') { + const code = shortcodeOf(x.emoji); + const url = code && (x.emojiUrl || emojiUrl(code)); + if (!url) return t('alertReact', { emoji: x.emoji }); + const [a, b = ''] = t('alertReact', { emoji: '\u0000' }).split('\u0000'); + return h('span', {}, a, reactImg(code, url), b); + } if (x.what === 'boost') return t('alertBoost'); if (x.what === 'reply') return t('alertReply'); return t('alertMention'); -- cgit v1.2.3