From cd7bced8276369fa6a2657a1b71c5504e367bf5e Mon Sep 17 00:00:00 2001 From: l5y <220195275+l5yth@users.noreply.github.com> Date: Sun, 16 Nov 2025 15:31:17 +0100 Subject: [PATCH] Added reaction-aware handling (#455) --- .../js/app/__tests__/message-replies.test.js | 29 ++++++++ web/public/assets/js/app/message-replies.js | 68 +++++++++++++++++-- 2 files changed, 91 insertions(+), 6 deletions(-) diff --git a/web/public/assets/js/app/__tests__/message-replies.test.js b/web/public/assets/js/app/__tests__/message-replies.test.js index 9755764..7ab668f 100644 --- a/web/public/assets/js/app/__tests__/message-replies.test.js +++ b/web/public/assets/js/app/__tests__/message-replies.test.js @@ -73,3 +73,32 @@ test('resolveReplyPrefix renders reply badge and buildMessageBody joins emoji', assert.equal(body, 'ESC(Hello) EMOJI(🔥)'); }); + +test('buildMessageBody suppresses reaction slot markers and formats counts', () => { + const reaction = { + text: ' 1 ', + emoji: '👍', + portnum: 'REACTION_APP', + reply_id: 123, + }; + const body = buildMessageBody({ + message: reaction, + escapeHtml: value => `ESC(${value})`, + renderEmojiHtml: value => `EMOJI(${value})` + }); + + assert.equal(body, 'EMOJI(👍)'); + + const countedReaction = { + text: '2', + emoji: '✨', + reply_id: 123 + }; + const countedBody = buildMessageBody({ + message: countedReaction, + escapeHtml: value => `ESC(${value})`, + renderEmojiHtml: value => `EMOJI(${value})` + }); + + assert.equal(countedBody, 'ESC(×2) EMOJI(✨)'); +}); diff --git a/web/public/assets/js/app/message-replies.js b/web/public/assets/js/app/message-replies.js index 23b3389..0981fd2 100644 --- a/web/public/assets/js/app/message-replies.js +++ b/web/public/assets/js/app/message-replies.js @@ -279,6 +279,63 @@ function normaliseEmojiValue(value) { return str.length > 0 ? str : null; } +/** + * Identify whether ``message`` represents a reaction payload. + * + * @param {?Object} message Message payload. + * @returns {boolean} True when the payload is a reaction. + */ +function isReactionMessage(message) { + if (!message || typeof message !== 'object') { + return false; + } + const portnum = toTrimmedString(message.portnum ?? message.portNum); + if (portnum && portnum.toUpperCase() === 'REACTION_APP') { + return true; + } + const hasEmoji = !!normaliseEmojiValue(message.emoji); + if (!hasEmoji) { + return false; + } + return message.reply_id != null || message.replyId != null || !!portnum; +} + +/** + * Derive the message text segment, suppressing reaction placeholders. + * + * @param {?Object} message Message payload. + * @param {boolean} isReaction Whether the payload is a reaction. + * @returns {?string} Text segment to render. + */ +function resolveMessageTextSegment(message, isReaction) { + if (!message || typeof message !== 'object') { + return null; + } + if (message.text == null) { + return null; + } + const textString = String(message.text); + if (textString.length === 0) { + return null; + } + if (!isReaction) { + return textString; + } + + const trimmed = textString.trim(); + if (trimmed.length === 0) { + return null; + } + const parsed = Number.parseInt(trimmed, 10); + if (Number.isFinite(parsed)) { + if (parsed <= 1) { + return null; + } + return `×${parsed}`; + } + return trimmed; +} + /** * Build the rendered message body containing text and optional emoji. * @@ -301,14 +358,13 @@ export function buildMessageBody({ message, escapeHtml, renderEmojiHtml }) { } const segments = []; - if (message.text != null) { - const textString = String(message.text); - if (textString.length > 0) { - segments.push(escapeHtml(textString)); - } + const reaction = isReactionMessage(message); + const textSegment = resolveMessageTextSegment(message, reaction); + if (textSegment) { + segments.push(escapeHtml(textSegment)); } const emoji = normaliseEmojiValue(message.emoji); - if (emoji && emoji!=='1') { + if (emoji) { segments.push(renderEmojiHtml(emoji)); }