diff options
| author | Adam Soltys <[email protected]> | 2026-09-17 15:33:46 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-09-17 15:33:46 -0700 |
| commit | ec3bd3e3155d312d88f0f215b24ab131901b36b1 (patch) | |
| tree | 249a9cbd7cd5977a3240bb9c8682a32ce9a56626 /src | |
| parent | 2f8794174fd4c441bf6c4b7553b4458cf23631ea (diff) | |
| download | coinosv3-ec3bd3e3155d312d88f0f215b24ab131901b36b1.tar.gz coinosv3-ec3bd3e3155d312d88f0f215b24ab131901b36b1.zip | |
Feed: the zap tally rides on the action-bar bolt, not the header.
The sats total used to sit up by the timestamp as its own chip; now it is the
count on the zap button, like likes and boosts have theirs. Amber once anyone
has zapped, breathing while ours is in flight, and shown read-only (no zap
handler) on posts you can't zap so the number still reads.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Diffstat (limited to 'src')
| -rw-r--r-- | src/features/messages.js | 27 | ||||
| -rw-r--r-- | src/style.css | 11 |
2 files changed, 32 insertions, 6 deletions
diff --git a/src/features/messages.js b/src/features/messages.js index 0e94b87..acde0cb 100644 --- a/src/features/messages.js +++ b/src/features/messages.js @@ -4455,14 +4455,24 @@ export function messagesFeature(ctx) { const rm = reacts.get(ev.id); const likeN = rm ? [...rm.values()].reduce((n, who) => n + who.size, 0) : 0; const boostN = (boosts.get(ev.id) || new Set()).size; - const btn = (icon, label, count, on, onClick) => h('button', { - class: 'note-act' + (on ? ' on' : ''), title: label, 'aria-label': label, - onClick: (e) => { e.stopPropagation(); onClick(); }, + const btn = (icon, label, count, on, onClick, cls = '') => h('button', { + class: 'note-act' + (on ? ' on' : '') + (cls ? ' ' + cls : ''), title: label, 'aria-label': label, + 'aria-disabled': onClick ? undefined : 'true', + onClick: (e) => { e.stopPropagation(); if (onClick) onClick(); }, }, typeof icon === 'string' && icon.startsWith('<svg') ? h('span', { style: 'display:flex', html: icon }) : h('span', { class: 'note-act-emoji' }, icon), count ? h('span', { class: 'note-act-n' }, String(count)) : null); + // the sats tally rides on the zap button, like the like and boost counts + // do on theirs — amber once anyone has zapped, breathing while ours flies + const z = zapTotals.get(ev.id) || zapSeeds().get(ev.id); + const zp = pendingOf(ev.id); + const optimistic = zp && zp.state !== 'void' ? zp.sats : 0; + const zapSats = (z ? z.sats : 0) + optimistic; + const zapFlying = !!zp && zp.state === 'flying'; + const zapMine = !!((z && z.mine) || optimistic); + const zapLabel = zapFlying ? t('zapSending') : zapSats ? t('zapTallyTitle', { n: zapSats.toLocaleString() }) : t('zapTitle'); return h('div', { class: 'row note-acts' }, btn(I_REPLY, t('msgReply'), 0, false, () => replyToNote(ev)), btn(I_BOOST, t('postBoost'), boostN, iBoosted(ev.id), () => boostNote(ev).catch(() => {})), @@ -4470,7 +4480,13 @@ export function messagesFeature(ctx) { // tap to choose how you feel about it; tap again to take it back btn(mineReact || I_HEART(false), t('postLike'), likeN, !!mineReact, () => { if (mineReact) unreact(ev).catch(() => {}); else { ui.reactPick = ev; render(); } }), - canZap ? btn(I_ZAP, t('zapTitle'), 0, false, () => { zapNote(pk, ev); recheckZap(ev.id); }) : null, + // shown whenever there is a tally to read, even where zapping is off + // (own post, no wallet) — then it is just a number, not a button + canZap || zapSats + ? btn(I_ZAP, zapLabel, zapSats ? fmtSats(zapSats) : 0, zapMine, + canZap ? () => { zapNote(pk, ev); recheckZap(ev.id); } : null, + 'note-zap' + (zapSats ? ' zapped' : '') + (zapFlying ? ' flying' : '')) + : null, // who did all that: a small chevron, only once there is anyone to show whoCount(ev.id) ? h('button', { @@ -4581,8 +4597,7 @@ export function messagesFeature(ctx) { onClick: (e) => { e.stopPropagation(); openProfile(pk); }, }, name), h('span', { class: 'small faint', style: 'white-space:nowrap' }, - (isReply ? '↩ ' + t('profReplyTag') + ' · ' : '') + timeLabel(ev.created_at * 1000)), - zapChip(ev.id, { onClick: canZap ? () => { zapNote(pk, ev); recheckZap(ev.id); } : null })), + (isReply ? '↩ ' + t('profReplyTag') + ' · ' : '') + timeLabel(ev.created_at * 1000))), pending ? null : h('button', { // the overflow stays up here; reply, boost, quote, react and zap // are a row of their own under the post diff --git a/src/style.css b/src/style.css index bb90480..3fd3de2 100644 --- a/src/style.css +++ b/src/style.css @@ -560,6 +560,17 @@ hr.divider { margin: 16px 0; } .note-act:hover { background: var(--surface2); color: var(--ink); } .note-act.on { color: #e0245e; } .note-act.on[aria-label="Boost"] { color: var(--green); } +/* the zap button carries the sats tally: amber once anyone has zapped, the + number in amber too; breathing while our own zap is in the air */ +.note-act.note-zap.zapped, .note-act.note-zap.zapped .note-act-n { color: #e6a800; } +.note-act.note-zap[aria-disabled] { cursor: default; } +.note-act.note-zap[aria-disabled]:hover { background: none; color: var(--faint); } +.note-act.note-zap.zapped[aria-disabled]:hover { color: #e6a800; } +.note-act.note-zap.flying { animation: zap-breathe 1.15s ease-in-out infinite; } +.note-act.note-zap.flying svg { animation: zap-flicker 1.15s ease-in-out infinite; transform-origin: center; } +@media (prefers-reduced-motion: reduce) { + .note-act.note-zap.flying, .note-act.note-zap.flying svg { animation: none; opacity: .7; } +} .note-act-emoji { font-size: 17px; line-height: 1; } .note-act-n { font-size: 12px; font-weight: 600; color: var(--muted); } .note-who-toggle.on { color: var(--muted); } |
