From 4e4aa7f580b6fd30dbd580a41efda6376da9eecd Mon Sep 17 00:00:00 2001 From: Muhsin Keloth Date: Thu, 3 Jul 2025 11:32:13 +0530 Subject: [PATCH 1/7] fix: Handle emoji and special characters in mention notifications (#11857) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes notification display issues when user or team names contain emojis and special characters. Previously, mention notifications would show URL-encoded characters instead of properly formatted names with emojis. **Before:** Notification: "John Doe: Hey @%F0%9F%91%8D%20customer%20support please check this" **After:** Notification: "John Doe: Hey @👍 customer support please check this" --- app/helpers/message_format_helper.rb | 10 +++++-- lib/regex_helper.rb | 8 +++++- spec/helpers/message_format_helper_spec.rb | 30 +++++++++++++++++++- spec/models/notification_spec.rb | 33 ++++++++++++++++++++++ 4 files changed, 76 insertions(+), 5 deletions(-) diff --git a/app/helpers/message_format_helper.rb b/app/helpers/message_format_helper.rb index 2c50fd609..8b271e3a3 100644 --- a/app/helpers/message_format_helper.rb +++ b/app/helpers/message_format_helper.rb @@ -1,9 +1,13 @@ module MessageFormatHelper - include RegexHelper - def transform_user_mention_content(message_content) # attachment message without content, message_content is nil - message_content.presence ? message_content.gsub(MENTION_REGEX, '\1') : '' + return '' unless message_content.presence + + # Use CommonMarker to convert markdown to plain text for notifications + # This handles all markdown formatting (links, bold, italic, etc.) not just mentions + # Converts: [@👍 customer support](mention://team/1/%F0%9F%91%8D%20customer%20support) + # To: @👍 customer support + CommonMarker.render_doc(message_content).to_plaintext.strip end def render_message_content(message_content) diff --git a/lib/regex_helper.rb b/lib/regex_helper.rb index 31916da7d..5e4c2abf0 100644 --- a/lib/regex_helper.rb +++ b/lib/regex_helper.rb @@ -5,7 +5,13 @@ module RegexHelper # valid unicode letter, unicode number, underscore, hyphen # shouldn't start with a underscore or hyphen UNICODE_CHARACTER_NUMBER_HYPHEN_UNDERSCORE = Regexp.new('\A[\p{L}\p{N}]+[\p{L}\p{N}_-]+\Z') - MENTION_REGEX = Regexp.new('\[(@[\w_. ]+)\]\(mention://(?:user|team)/\d+/(.*?)+\)') + # Regex to match mention markdown links and extract display names + # Matches: [@display name](mention://user|team/id/url_encoded_name) + # Captures: 1) @display name (including emojis), 2) url_encoded_name + # Uses [^]]+ to match any characters except ] in display name to support emojis + # NOTE: Still used by Slack integration (lib/integrations/slack/send_on_slack_service.rb) + # while notifications use CommonMarker for better markdown processing + MENTION_REGEX = Regexp.new('\[(@[^]]+)\]\(mention://(?:user|team)/\d+/([^)]+)\)') TWILIO_CHANNEL_SMS_REGEX = Regexp.new('^\+\d{1,15}\z') TWILIO_CHANNEL_WHATSAPP_REGEX = Regexp.new('^whatsapp:\+\d{1,15}\z') diff --git a/spec/helpers/message_format_helper_spec.rb b/spec/helpers/message_format_helper_spec.rb index c5bafe67d..fe1335b8f 100644 --- a/spec/helpers/message_format_helper_spec.rb +++ b/spec/helpers/message_format_helper_spec.rb @@ -3,9 +3,37 @@ require 'rails_helper' describe MessageFormatHelper do describe '#transform_user_mention_content' do context 'when transform_user_mention_content called' do - it 'return transormed text correctly' do + it 'return transformed text correctly' do expect(helper.transform_user_mention_content('[@john](mention://user/1/John%20K), check this ticket')).to eq '@john, check this ticket' end + + it 'handles emoji in display names correctly' do + content = '[@👍 customer support](mention://team/1/%F0%9F%91%8D%20customer%20support), please help' + expected = '@👍 customer support, please help' + expect(helper.transform_user_mention_content(content)).to eq expected + end + + it 'handles multiple mentions with emojis and spaces' do + content = 'Hey [@John Doe](mention://user/1/John%20Doe) and [@🚀 Dev Team](mention://team/2/%F0%9F%9A%80%20Dev%20Team)' + expected = 'Hey @John Doe and @🚀 Dev Team' + expect(helper.transform_user_mention_content(content)).to eq expected + end + + it 'handles emoji-only team names' do + expect(helper.transform_user_mention_content('[@🔥](mention://team/3/%F0%9F%94%A5) urgent')).to eq '@🔥 urgent' + end + + it 'handles special characters in names' do + expect(helper.transform_user_mention_content('[@user@domain.com](mention://user/4/user%40domain.com) check')).to eq '@user@domain.com check' + end + + it 'returns empty string for nil content' do + expect(helper.transform_user_mention_content(nil)).to eq '' + end + + it 'returns empty string for empty content' do + expect(helper.transform_user_mention_content('')).to eq '' + end end end diff --git a/spec/models/notification_spec.rb b/spec/models/notification_spec.rb index 07e7e3fe2..172389fe4 100644 --- a/spec/models/notification_spec.rb +++ b/spec/models/notification_spec.rb @@ -128,6 +128,39 @@ has been assigned to you" expect(notification.push_message_body).to eq "#{message.sender.name}: Hey @John Peter please check this?" end + it 'returns appropriate body suited for the notification type conversation_mention if username contains emoji' do + conversation = create(:conversation) + content = 'Hey [@👍 customer support](mention://team/1/%F0%9F%91%8D%20customer%20support) please check this?' + message = create(:message, sender: create(:user), content: content, conversation: conversation) + notification = create(:notification, notification_type: 'conversation_mention', primary_actor: conversation, secondary_actor: message) + expect(notification.push_message_body).to eq "#{message.sender.name}: Hey @👍 customer support please check this?" + end + + it 'returns appropriate body suited for the notification type conversation_mention if team name contains emoji and spaces' do + conversation = create(:conversation) + content = 'Please check [@🚀 Development Team](mention://team/2/%F0%9F%9A%80%20Development%20Team)' + message = create(:message, sender: create(:user), content: content, conversation: conversation) + notification = create(:notification, notification_type: 'conversation_mention', primary_actor: conversation, secondary_actor: message) + expect(notification.push_message_body).to eq "#{message.sender.name}: Please check @🚀 Development Team" + end + + it 'returns appropriate body suited for the notification type conversation_mention with mixed emoji and regular mentions' do + conversation = create(:conversation) + content = 'Hey [@John Doe](mention://user/1/John%20Doe) and ' \ + '[@👍 customer support](mention://team/1/%F0%9F%91%8D%20customer%20support) please review' + message = create(:message, sender: create(:user), content: content, conversation: conversation) + notification = create(:notification, notification_type: 'conversation_mention', primary_actor: conversation, secondary_actor: message) + expect(notification.push_message_body).to eq "#{message.sender.name}: Hey @John Doe and @👍 customer support please review" + end + + it 'returns appropriate body suited for the notification type conversation_mention with special characters in names' do + conversation = create(:conversation) + content = 'Please review [@user@domain.com](mention://user/4/user%40domain.com)' + message = create(:message, sender: create(:user), content: content, conversation: conversation) + notification = create(:notification, notification_type: 'conversation_mention', primary_actor: conversation, secondary_actor: message) + expect(notification.push_message_body).to eq "#{message.sender.name}: Please review @user@domain.com" + end + it 'calls remove duplicate notification job' do allow(Notification::RemoveDuplicateNotificationJob).to receive(:perform_later) notification = create(:notification, notification_type: 'conversation_mention') From cb01a2bc66d3f0376a56190799472522518d6986 Mon Sep 17 00:00:00 2001 From: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Date: Thu, 3 Jul 2025 13:22:44 +0530 Subject: [PATCH 2/7] fix: Incorrect account ID in conversation header back button URL (#11866) --- .../widgets/conversation/ConversationHeader.vue | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/app/javascript/dashboard/components/widgets/conversation/ConversationHeader.vue b/app/javascript/dashboard/components/widgets/conversation/ConversationHeader.vue index 7a3489265..cdcbd64b5 100644 --- a/app/javascript/dashboard/components/widgets/conversation/ConversationHeader.vue +++ b/app/javascript/dashboard/components/widgets/conversation/ConversationHeader.vue @@ -39,15 +39,21 @@ const chatMetadata = computed(() => props.chat.meta); const backButtonUrl = computed(() => { const { - params: { inbox_id: inboxId, label, teamId }, + params: { inbox_id: inboxId, label, teamId, id: customViewId }, name, } = route; + + const conversationTypeMap = { + conversation_through_mentions: 'mention', + conversation_through_unattended: 'unattended', + }; return conversationListPageURL({ - accountId, + accountId: accountId.value, inboxId, label, teamId, - conversationType: name === 'conversation_mentions' ? 'mention' : '', + conversationType: conversationTypeMap[name], + customViewId, }); }); From 7343e53659e9020024653914157ed707a0c07f89 Mon Sep 17 00:00:00 2001 From: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Date: Thu, 3 Jul 2025 19:39:36 +0530 Subject: [PATCH 3/7] fix: Variable search item not showing after braces/commas (#11864) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Pull Request Template ## Description This PR fixes an issue where typing variables, like `{{contact.name}}`, caused the variable list to miss showing `contact.name`. The search key in this case became `contact.name}},` which didn't match any available options. The logic in `VariableList.vue` only checked the part after the last comma and didn’t fully sanitize the input. **Solution** Updated `searchKey` to remove all {} and commas for accurate matching. Fixes [CW-4574](https://linear.app/chatwoot/issue/CW-4574/i-dont-see-an-option-for-contactname-it-shows-initially-but-it-doesnt) ## Type of change - [x] Bug fix (non-breaking change which fixes an issue) ## How Has This Been Tested? ### Loom video https://www.loom.com/share/fc86e53853ad49e6acf6de57ebbd8fcb?sid=6702f896-d1a3-4c5a-9eb7-b96b5ed91531 ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules --- .../widgets/conversation/VariableList.vue | 8 +++-- app/javascript/dashboard/helper/commons.js | 13 +++++++ .../dashboard/helper/specs/commons.spec.js | 35 +++++++++++++++++++ 3 files changed, 54 insertions(+), 2 deletions(-) diff --git a/app/javascript/dashboard/components/widgets/conversation/VariableList.vue b/app/javascript/dashboard/components/widgets/conversation/VariableList.vue index 1b935ad29..30e45749b 100644 --- a/app/javascript/dashboard/components/widgets/conversation/VariableList.vue +++ b/app/javascript/dashboard/components/widgets/conversation/VariableList.vue @@ -1,6 +1,7 @@