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": {