diff options
| author | Adam Soltys <[email protected]> | 2026-10-09 08:05:28 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-10-09 08:05:28 -0700 |
| commit | fc1cf42a477d6a4f179cac5461373cbd52de7eb1 (patch) | |
| tree | 74df1ad19a2af47496ff140da0697016cce22811 | |
| parent | 817cc4a2dc0c7d343864da594aeb3c32b3459ffb (diff) | |
| download | coinosv3-fc1cf42a477d6a4f179cac5461373cbd52de7eb1.tar.gz coinosv3-fc1cf42a477d6a4f179cac5461373cbd52de7eb1.zip | |
Chat: a one-tap ⚡ beside every bubble from someone else, in communities and DMs, vertically centred on the bubble — tap zaps the default amount (the strike starts at the bolt), hold sets the amount, without opening the message sheet first. Own messages, visitors and wallets that can't zap get none; Marmot groups have no zap yet and are left alone
Co-Authored-By: Claude Opus 5.5 <[email protected]>
| -rw-r--r-- | src/features/messages.js | 23 | ||||
| -rw-r--r-- | src/style.css | 12 |
2 files changed, 30 insertions, 5 deletions
diff --git a/src/features/messages.js b/src/features/messages.js index e9e238a..1fef866 100644 --- a/src/features/messages.js +++ b/src/features/messages.js @@ -4208,10 +4208,21 @@ export function messagesFeature(ctx) { } // Zap a chat message or DM: same one-tap flow as a post (ark first, // Lightning fallback, the amount remembered from the first time). - function zapMessage(pk, id) { - zapNote(pk, { id }); + function zapMessage(pk, id, origin) { + zapNote(pk, { id }, origin); recheckZap(id); } + // The one-tap ⚡ beside someone else's bubble: zap without opening the + // message sheet first (tap pays the default, hold sets the amount). + function msgZapBtn(pk, id) { + if (!canZapPk(pk) || isMe(pk)) return null; + if (zapSoundOn()) warmZapSound(); + return h('button', { + class: 'chat-zap', title: t('msgZap'), 'aria-label': t('msgZap'), + ...holdable((el) => openZapSettings(pk, id, el.getBoundingClientRect()), + (e) => { e.stopPropagation(); zapMessage(pk, id, e.currentTarget.getBoundingClientRect()); }), + }, h('span', { style: 'display:flex', html: BOLT_SVG })); + } const notesCache = new Map(); // pk -> { status: 'loading'|'ready', notes: [kind-1 events] } // NIP-65: where this author actually writes. Their notes and threads live // there first — our default relays are just the common ground. @@ -12055,7 +12066,7 @@ export function messagesFeature(ctx) { displayName(m.author), m.author === room.jm.owner ? h('span', { class: 'chat-badge' }, t('msgAdmin')) : null), h('span', { class: 'chat-time' }, timeLabel(tms))), - h('div', { + h('div', { class: 'chat-line' }, h('div', { class: 'chat-bubble clickable' + (emojiJumbo(text, em) ? ' jumbo' : ''), // Telegram-style: a tap on the message opens its action sheet // (quick reactions, reply, copy, delete). Links, images and the @@ -12089,7 +12100,8 @@ export function messagesFeature(ctx) { title: t('msgReact'), onClick: (e) => { e.stopPropagation(); sendReaction(room, chId, m, emoji); }, }, reactNode(emoji), n > 1 ? ' ' + n : ''))) - : null)(zapChip(m.rumor.id, { cls: 'chat-react', onClick: canZapPk(m.author) ? () => zapMessage(m.author, m.rumor.id) : null, onHold: () => openZapSettings(m.author, m.rumor.id) })))) + : null)(zapChip(m.rumor.id, { cls: 'chat-react', onClick: canZapPk(m.author) ? () => zapMessage(m.author, m.rumor.id) : null, onHold: () => openZapSettings(m.author, m.rumor.id) }))), + msgZapBtn(m.author, m.rumor.id))) ); }); } @@ -12642,7 +12654,7 @@ export function messagesFeature(ctx) { ? msgs.map((m) => h('div', { class: 'chat-row dm' + (m.mine ? ' mine' : '') }, h('div', { class: 'chat-body' }, - h('div', { + h('div', { class: 'chat-line' }, h('div', { class: 'chat-bubble clickable' + (m.mine ? ' me' : '') + (emojiJumbo(m.rumor.content, emojiTagMap(m.rumor.tags)) ? ' jumbo' : ''), // same tap-for-actions as community bubbles onClick: (e) => { @@ -12653,6 +12665,7 @@ export function messagesFeature(ctx) { render(); }, }, dmQuote(m), ...(m.rumor.kind === 15 ? [] : noteBody(m.rumor.content, 0, emojiTagMap(m.rumor.tags))), ...attachmentNodes(m.rumor, m.wn), dmChips(m)), + m.mine ? null : msgZapBtn(m.rumor.pubkey, m.rumor.id)), h('div', { class: 'chat-time' }, timeLabel(m.rumor.created_at * 1000))))) : [h('div', { class: 'muted small', style: 'text-align:center;padding:24px 0' }, t('msgNoDmsYet'))])), dmReplyBar(), diff --git a/src/style.css b/src/style.css index 3b715ec..7a8d3c0 100644 --- a/src/style.css +++ b/src/style.css @@ -580,6 +580,18 @@ html:has(#app .chat-card), body:has(#app .chat-card) { overflow: hidden; } .zap-tally { display: inline-flex; align-items: center; gap: 2px; font-size: 11px; font-weight: 650; color: #e6a800; white-space: nowrap; flex-shrink: 0; } .zap-tally.chat-react { font-size: 12px; padding: 1px 7px 1px 5px; } .zap-tally.clickable { cursor: pointer; } +/* the one-tap ⚡ beside someone else's chat bubble */ +.chat-line { display: flex; align-items: center; gap: 4px; } +.chat-row.dm.mine .chat-line { justify-content: flex-end; } +.chat-line > .chat-bubble { min-width: 0; } +.chat-zap { + flex-shrink: 0; width: 28px; height: 28px; padding: 0; border: none; border-radius: 50%; + display: flex; align-items: center; justify-content: center; + background: none; color: var(--muted); cursor: pointer; + -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; +} +.chat-zap:hover { color: #e6a800; background: var(--paper); } +.chat-zap svg { width: 17px; height: 17px; } /* A tap-local lightning strike, followed by a floating amount and embers. */ .zap-fx { position: fixed; inset: 0; z-index: 10000; pointer-events: none; overflow: hidden; contain: strict; } .zap-fx-impact { position: absolute; left: var(--zap-x); top: var(--zap-y); width: 0; height: 0; color: #ffc83d; } |
