diff --git a/app/builders/messages/forwarded_message_builder.rb b/app/builders/messages/forwarded_message_builder.rb index c1afc976e..04e82f073 100644 --- a/app/builders/messages/forwarded_message_builder.rb +++ b/app/builders/messages/forwarded_message_builder.rb @@ -1,48 +1,255 @@ -# Handles formatting and preparation of forwarded email messages -class Messages::ForwardedMessageBuilder - def initialize(message_id) - @message_id = message_id +module Messages::ForwardedMessageFormatter + def self.parse_from_field(from_field) + return '' if from_field.blank? + + from_field =~ /(.*)<(.*)>/ ? Regexp.last_match(2).strip : from_field end - def perform - return {} unless @message_id - return basic_attributes unless valid_forwarded_data? + def self.format_plain_text_to_html(text) + ERB::Util.html_escape(text.to_s).gsub("\n", '
') + end - build_forwarded_attributes + def self.strip_markdown(text) + return '' if text.blank? + + text.gsub(/\*\*?(.*?)\*\*?|_(.*?)_/) { |_m| Regexp.last_match(1) || Regexp.last_match(2) } + end + + def self.convert_markdown_to_html(text) + return '' if text.blank? + + html = text.to_s + html.gsub!(/\*\*?(.*?)\*\*?/) do |m| + m.start_with?('**') ? "#{Regexp.last_match(1)}" : "#{Regexp.last_match(1)}" + end + html.gsub(/_(.*?)_/, '\1') + end + + def self.extract_email(from_field) + return '' if from_field.blank? + + from_field =~ /<(.*)>/ ? Regexp.last_match(1) : from_field + end + + def self.format_date_string(date_str) + return '' if date_str.blank? + + begin + parsed_date = DateTime.now + parsed_date.strftime('%a, %b %-d, %Y at %-l:%M %p') + rescue StandardError + date_str + end + end +end + +module Messages::ForwardedMessageHtmlBuilder + def html_wrapper(content) + base = '
' + if content.blank? + "#{base}#{forwarded_header_html}#{forwarded_body_html}
" + else + "#{base}#{content}

#{forwarded_header_html}#{forwarded_body_html}" + end + end + + def forwarded_header_html + from_value = formatted_info[:from].to_s + email = Messages::ForwardedMessageFormatter.extract_email(from_value) + + [ + '
', + '
---------- Forwarded message ---------
', + build_email_html(email), + build_field_html('Date', formatted_info[:date]), + build_field_html('Subject', formatted_info[:subject]), + build_to_html(formatted_info[:to]), + '


' + ].compact.join + end + + def build_email_html(email) + return nil if email.blank? + + "From: <#{email}>
" + end + + def build_field_html(label, value) + value.present? ? "#{label}: #{value}
" : nil + end + + def build_to_html(to_email) + return nil if to_email.blank? + + "To: <#{to_email}>
" + end + + def forwarded_body_html + # Return HTML content directly if available + return email_data.dig('html_content', 'full') if email_data&.dig('html_content', 'full').present? + + # Otherwise format plain text to HTML + content = if email_data&.dig('text_content', 'full').present? + email_data.dig('text_content', 'full') + else + forwarded_message.content.to_s + end + + Messages::ForwardedMessageFormatter.format_plain_text_to_html(content) + end +end + +module Messages::ForwardedMessageContentBuilder + def forwarded_header_text + return '' unless formatted_info.values.any? + + [ + "\n\n---------- Forwarded message ---------", + ("From: #{formatted_info[:from]}" if formatted_info[:from]), + ("Date: #{formatted_info[:date]}" if formatted_info[:date]), + ("Subject: #{formatted_info[:subject]}" if formatted_info[:subject]), + ("To: <#{formatted_info[:to]}>" if formatted_info[:to]), + "\n" + ].compact.join("\n") + end + + def forwarded_body_text + return forwarded_message.content.to_s if email_data.blank? + + text = email_data.dig('text_content', 'full') + html = email_data.dig('html_content', 'full') + if text.present? + text + elsif html.present? + ActionView::Base.full_sanitizer.sanitize(html) + else + forwarded_message.content.to_s + end end def formatted_content(original_content = '') - return original_content.to_s unless valid_forwarded_data? + return original_content.to_s if forwarded_message.blank? original_content.to_s + forwarded_header_text + forwarded_body_text end def formatted_html_content(original_content = '') - return original_content unless valid_forwarded_data? + return original_content if forwarded_message.blank? - html_content = convert_markdown_to_html(original_content) - html_wrapper(html_content) + html_wrapper(Messages::ForwardedMessageFormatter.convert_markdown_to_html(original_content)) + end +end + +module Messages::ForwardedMessageDataHandler + def prepare_email_data + initialize_email_data + end + + def initialize_email_data + data = base_email_data + add_content_fields(data) + add_header_fields(data) + data + end + + def base_email_data + email_data.present? ? email_data.dup || {} : {} + end + + def add_content_fields(data) + data['html_content'] ||= {} + data['text_content'] ||= {} + end + + def add_header_fields(data) + data['from'] = [email_from_inbox] # Always overwrite with inbox email + data['to'] = Array(@params[:to_emails]) if @params && @params[:to_emails].present? + data['subject'] ||= subject + data['date'] ||= Time.zone.now.to_s + end + + def formatted_info + { + from: inbox_email.to_s, + date: Messages::ForwardedMessageFormatter.format_date_string(email_date), + subject: subject.to_s, + to: recipient_email.to_s + } + end + + def inbox_email + email_from_data || email_from_inbox + end + + def email_from_data + return nil unless email_data_has_from? + + from_field = email_data['from'].first.to_s + Messages::ForwardedMessageFormatter.parse_from_field(from_field) + end + + def email_data_has_from? + email_data.present? && + email_data['from'].present? && + email_data['from'].first.present? + end + + def email_from_inbox + inbox = forwarded_message&.conversation&.inbox + return nil if inbox.blank? || inbox.channel_type != 'Channel::Email' + + email = inbox.channel&.email + return nil if email.blank? + + email + end + + def recipient_email + return email_data&.dig('to', 0) if email_data&.dig('to', 0).present? + + forwarded_message&.content_attributes&.dig('to_emails', 0).presence + end + + def subject + email_data&.dig('subject').presence || + forwarded_message&.conversation&.additional_attributes&.dig('subject').presence || + 'No Subject' + end + + def email_date + email_data&.dig('date').presence || Time.zone.now.to_s + end +end + +class Messages::ForwardedMessageBuilder + include Messages::ForwardedMessageFormatter + include Messages::ForwardedMessageHtmlBuilder + include Messages::ForwardedMessageContentBuilder + include Messages::ForwardedMessageDataHandler + + def initialize(message_id, params = {}) + @message_id = message_id + @params = params || {} + end + + def perform + return {} unless @message_id + return basic_attributes if forwarded_message.blank? + + build_forwarded_attributes end def forwarded_email_data(original_content = '') - return {} unless valid_forwarded_data? + return {} if forwarded_message.blank? data = prepare_email_data full_content = formatted_content(original_content) + stripped = Messages::ForwardedMessageFormatter.strip_markdown(original_content.to_s) + html_full = formatted_html_content(original_content) - data['html_content'].merge!({ - 'quoted' => strip_markdown(original_content.to_s), - 'reply' => full_content, - 'full' => formatted_html_content(original_content) - }) + data['html_content'].merge!('quoted' => stripped, 'reply' => full_content, 'full' => html_full) + data['text_content'].merge!('quoted' => original_content.to_s, 'reply' => full_content, 'full' => full_content) - data['text_content'].merge!({ - 'quoted' => original_content.to_s, - 'reply' => full_content, - 'full' => full_content - }) - - # Copy over any attachment data if present data['attachments'] = forwarded_message.attachments.map(&:serializable_hash) if forwarded_message.attachments.present? data @@ -54,106 +261,8 @@ class Messages::ForwardedMessageBuilder private - def valid_forwarded_data? - forwarded_message && email_data.present? - end - - def html_wrapper(content) - base = '
' - - if content.blank? - "#{base}#{forwarded_header_html}#{forwarded_body_html}
" - else - "#{base}#{content}

#{forwarded_header_html}#{forwarded_body_html}
" - end - end - def build_forwarded_attributes - { - content_attributes: { - forwarded_message_id: @message_id, - email: prepare_email_data - } - } - end - - def forwarded_header_text - [ - "\n\n---------- Forwarded message ---------", - "From: #{formatted_info[:from]}", - "Date: #{formatted_info[:date]}", - "Subject: #{formatted_info[:subject]}", - "To: <#{formatted_info[:to]}>\n\n" - ].join("\n") - end - - def forwarded_body_text - text_content = email_data.dig('text_content', 'full') - html_content = email_data.dig('html_content', 'full') - - if text_content.present? - text_content - elsif html_content.present? - ActionView::Base.full_sanitizer.sanitize(html_content) - else - forwarded_message.content.to_s - end - end - - def forwarded_header_html - email = extract_email(formatted_info[:from]) - name = formatted_info[:from].split(' <').first - - [ - '
', - '
---------- Forwarded message ---------
', - "From: #{name} ", - "<#{email}>
", - "Date: #{formatted_info[:date]}
", - "Subject: #{formatted_info[:subject]}
", - "To: <#{formatted_info[:to]}>
", - '


' - ].join - end - - def forwarded_body_html - if email_data.dig('html_content', 'full').present? - email_data.dig('html_content', 'full') - elsif email_data.dig('text_content', 'full').present? - "
#{ERB::Util.html_escape(email_data.dig('text_content', 'full'))}
" - else - "
#{ERB::Util.html_escape(forwarded_message.content.to_s)}
" - end - end - - def prepare_email_data - data = email_data.dup || {} - data['html_content'] ||= {} - data['text_content'] ||= {} - data - end - - def strip_markdown(text) - return '' if text.blank? - - text.gsub(/\*\*?(.*?)\*\*?|_(.*?)_/) { |_m| ::Regexp.last_match(1) || ::Regexp.last_match(2) } - end - - def convert_markdown_to_html(text) - return '' if text.blank? - - html = text.to_s - html = html.gsub(/\*\*?(.*?)\*\*?/) do |m| - m.start_with?('**') ? "#{::Regexp.last_match(1)}" : "#{::Regexp.last_match(1)}" - end - - html.gsub(/_(.*?)_/, '\1') - end - - def extract_email(from_field) - return '' if from_field.blank? - - from_field =~ /<(.*)>/ ? ::Regexp.last_match(1) : from_field + { content_attributes: { forwarded_message_id: @message_id, email: prepare_email_data } } end def forwarded_message @@ -167,38 +276,4 @@ class Messages::ForwardedMessageBuilder def basic_attributes { content_attributes: { forwarded_message_id: @message_id } } end - - def formatted_info - { - from: parse_from_field(extract_from_field), - date: format_date_string(email_data['date'] || ''), - subject: email_data['subject'] || '', - to: email_data['to']&.first || '' - } - end - - def extract_from_field - email_data['from']&.first.to_s - end - - def parse_from_field(from_field) - return '' if from_field.blank? - - if from_field =~ /(.*)<(.*)>/ - "#{::Regexp.last_match(1).strip} <#{::Regexp.last_match(2).strip}>" - else - from_field - end - end - - def format_date_string(date_str) - return '' if date_str.blank? - - begin - parsed_date = DateTime.now # Use current date - parsed_date.strftime('%a, %b %-d, %Y at %-l:%M %p') - rescue StandardError - date_str - end - end end diff --git a/app/javascript/dashboard/components-next/message/forwardMessage/ForwardMessage.vue b/app/javascript/dashboard/components-next/message/forwardMessage/ForwardMessage.vue index 2306f397a..803d0189f 100644 --- a/app/javascript/dashboard/components-next/message/forwardMessage/ForwardMessage.vue +++ b/app/javascript/dashboard/components-next/message/forwardMessage/ForwardMessage.vue @@ -53,7 +53,7 @@ const currentChat = useMapGetter('getSelectedChat'); const currentUser = useMapGetter('getCurrentUser'); const globalConfig = useMapGetter('globalConfig/get'); -const fromEmail = computed(() => props.message?.to?.[0] || props.inbox?.email); +const fromEmail = computed(() => props.inbox?.email); const fullHTML = computed(() => { return ( diff --git a/app/javascript/dashboard/components-next/message/forwardMessage/components/EmailMessageEditor.vue b/app/javascript/dashboard/components-next/message/forwardMessage/components/EmailMessageEditor.vue index ca2a724a7..c56d0933e 100644 --- a/app/javascript/dashboard/components-next/message/forwardMessage/components/EmailMessageEditor.vue +++ b/app/javascript/dashboard/components-next/message/forwardMessage/components/EmailMessageEditor.vue @@ -39,15 +39,9 @@ const showQuotedMessage = ref(false); />
-
- + {{ t('FORWARD_MESSAGE_FORM.FORWARDED_MESSAGE') }} -
diff --git a/app/javascript/dashboard/i18n/locale/en/conversation.json b/app/javascript/dashboard/i18n/locale/en/conversation.json index 5930fd0ac..4843a3b25 100644 --- a/app/javascript/dashboard/i18n/locale/en/conversation.json +++ b/app/javascript/dashboard/i18n/locale/en/conversation.json @@ -245,7 +245,7 @@ "FORWARD_MESSAGE_FORM": { "FROM": "From :", "EMAIL_EDITOR_PLACEHOLDER": "Write your message here...", - "FORWARDED_MESSAGE": "Forwarded message", + "FORWARDED_MESSAGE": "---------- Forwarded message ----------", "SHOW_QUOTED_TEXT": "Show quoted text", "HIDE_QUOTED_TEXT": "Hide quoted text", "CONTACT_SEARCH": {