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 492c4f8..9bc4f79 100644 --- a/web/public/assets/js/app/__tests__/message-replies.test.js +++ b/web/public/assets/js/app/__tests__/message-replies.test.js @@ -102,3 +102,52 @@ test('buildMessageBody suppresses reaction slot markers and formats counts', () assert.equal(countedBody, 'EMOJI(✨) ESC(×2)'); }); + +test('buildMessageBody treats REACTION_APP packets without reply identifiers as reactions', () => { + const reactionAppPacket = { + text: '1', + emoji: '🚀', + portnum: 'REACTION_APP' + }; + + const body = buildMessageBody({ + message: reactionAppPacket, + escapeHtml: value => `ESC(${value})`, + renderEmojiHtml: value => `EMOJI(${value})` + }); + + assert.equal(body, 'EMOJI(🚀)'); +}); + +test('buildMessageBody renders reaction emoji from text when emoji field carries placeholder counts', () => { + const placeholderEmojiMessage = { + text: '💩', + emoji: '1', + reply_id: 98822809, + portnum: 'TEXT_MESSAGE_APP' + }; + + const body = buildMessageBody({ + message: placeholderEmojiMessage, + escapeHtml: value => `ESC(${value})`, + renderEmojiHtml: value => `EMOJI(${value})` + }); + + assert.equal(body, 'EMOJI(💩)'); +}); + +test('buildMessageBody appends reaction counts for REACTION_APP packets without reply identifiers', () => { + const countedReactionAppPacket = { + text: '2', + emoji: '🌶', + portnum: 'REACTION_APP' + }; + + const body = buildMessageBody({ + message: countedReactionAppPacket, + escapeHtml: value => `ESC(${value})`, + renderEmojiHtml: value => `EMOJI(${value})` + }); + + assert.equal(body, 'EMOJI(🌶) ESC(×2)'); +}); diff --git a/web/public/assets/js/app/message-replies.js b/web/public/assets/js/app/message-replies.js index 01bb210..725a7b4 100644 --- a/web/public/assets/js/app/message-replies.js +++ b/web/public/assets/js/app/message-replies.js @@ -290,14 +290,16 @@ function isReactionMessage(message) { return false; } const portnum = toTrimmedString(message.portnum ?? message.portNum); - if (portnum && portnum.toUpperCase() === 'REACTION_APP') { + const reactionPort = portnum && portnum.toUpperCase() === 'REACTION_APP'; + if (reactionPort) { return true; } const hasEmoji = !!normaliseEmojiValue(message.emoji); if (!hasEmoji) { return false; } - return message.reply_id != null || message.replyId != null || !!portnum; + const hasReplyId = message.reply_id != null || message.replyId != null; + return hasReplyId || !!portnum; } /** @@ -357,20 +359,33 @@ export function buildMessageBody({ message, escapeHtml, renderEmojiHtml }) { return ''; } - const segments = []; + const segments = []; const reaction = isReactionMessage(message); const textSegment = resolveMessageTextSegment(message, reaction); const reactionCount = reaction && textSegment && /^×\d+$/.test(textSegment) ? textSegment : null; - if (textSegment && !reaction) { + const emoji = normaliseEmojiValue(message.emoji); + const emojiIsNumericPlaceholder = reaction && emoji && /^\d+$/.test(emoji); + let reactionEmoji = reaction && !emojiIsNumericPlaceholder ? emoji : null; + + if (!reaction && textSegment) { segments.push(escapeHtml(textSegment)); } - const emoji = normaliseEmojiValue(message.emoji); - if (emoji) { + + if (reaction) { + if (!reactionEmoji && textSegment && !reactionCount) { + reactionEmoji = textSegment; + } + if (reactionEmoji) { + segments.push(renderEmojiHtml(reactionEmoji)); + } else if (textSegment && !reactionCount) { + segments.push(escapeHtml(textSegment)); + } + if (reactionCount) { + segments.push(escapeHtml(reactionCount)); + } + } else if (emoji) { segments.push(renderEmojiHtml(emoji)); } - if (reactionCount) { - segments.push(escapeHtml(reactionCount)); - } if (segments.length === 0) { return '';