From d92dfd42e7b01fd2a4806175136d10872474ca5c Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Fri, 4 Sep 2026 09:26:00 -0700 Subject: Telegram-style message action sheet + replies in community chat. Tapping any bubble opens a centered sheet: a quick row of the six reaction emojis (your current one highlighted; picking fires immediately) above Reply / Copy / Delete (delete on own messages only) — replacing the hover-only 🙂 picker, which was invisible on the phones that need it most. Links, images and the hover × keep their own clicks, and a desktop text-selection drag never pops the sheet over the copy. REPLIES are new: Reply arms a composer bar showing who/what you're answering (× to cancel, cleared on channel/room switch), the sent kind-9 e-tags the quoted message, and rows carrying an e-tag render the quoted context above their text — author + one-line snippet, silent when the original is deleted or not held. The public /chat page renders the same quotes. Verified against the built stylesheet: 300px sheet, six even emoji buttons, quote ellipsizes, circular cancel button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01MGzU12QrAA4G6BYGzGVdXz --- src/public-chat.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'src/public-chat.js') diff --git a/src/public-chat.js b/src/public-chat.js index 5732f4a..d3dd042 100644 --- a/src/public-chat.js +++ b/src/public-chat.js @@ -237,6 +237,14 @@ export function mountPublicChat() { const edit = edits.get(m.rumor.id); const content = edit && edit.author === m.author ? edit.rumor.content : m.rumor.content; const reacts = reactions.get(m.rumor.id); + // a reply e-tags its quoted message — render the context when we hold it + const replyId = (m.rumor.tags || []).find((x) => x[0] === 'e')?.[1]; + const replySrc = replyId && !(deletes.get(replyId) || new Set()).size + ? (byChannel.get(activeChannel) || new Map()).get(replyId) : null; + const quote = replySrc ? h('div', { class: 'chat-quote' }, + h('span', { class: 'chat-quote-name' }, nameOf(replySrc.author)), + h('span', { class: 'chat-quote-text' }, + String(replySrc.rumor.content || '').replace(/\s+/g, ' ').slice(0, 90))) : null; log.append(h('div', { class: 'chat-row' + (grouped ? ' grouped' : '') }, grouped ? h('div', { class: 'chat-avatar spacer' }) : avatar(m.author), h('div', { class: 'chat-body' }, @@ -244,7 +252,7 @@ export function mountPublicChat() { h('span', { class: 'chat-name' + (m.author === COMMUNITY.owner ? ' owner' : '') }, nameOf(m.author)), m.author === COMMUNITY.owner ? h('span', { class: 'chat-badge' }, t('msgAdmin')) : null, h('span', { class: 'chat-time' }, timeAgo(ms / 1000))), - h('div', { class: 'chat-bubble' }, ...linkedBody(content), + h('div', { class: 'chat-bubble' }, quote, ...linkedBody(content), edit && edit.author === m.author ? h('span', { class: 'chat-edited' }, ' ' + t('msgEdited')) : null), reacts && reacts.size ? h('div', { class: 'chat-reacts' }, ...[...[...reacts.values()].reduce((m2, e) => m2.set(e, (m2.get(e) || 0) + 1), new Map()).entries()] -- cgit v1.2.3