diff --git a/app/builders/messages/forwarded_message_builder.rb b/app/builders/messages/forwarded_message_builder.rb new file mode 100644 index 000000000..945fd77d7 --- /dev/null +++ b/app/builders/messages/forwarded_message_builder.rb @@ -0,0 +1,212 @@ +# Handles formatting and preparation of forwarded email messages +class Messages::ForwardedMessageBuilder + def initialize(message_id) + @message_id = message_id + end + + def perform + return {} unless @message_id + return basic_attributes unless forwarded_message && email_data.present? + + build_forwarded_attributes + end + + def formatted_content(original_content = '') + return original_content unless forwarded_message && email_data.present? + + original_content = original_content.to_s + original_content + forwarded_header_text + forwarded_body_text + end + + def formatted_html_content(original_content = '') + return original_content unless forwarded_message && email_data.present? + + html_content = convert_markdown_to_html(original_content) + + # Ensure valid HTML structure even with empty content + return "
#{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?
+
+ # Basic markdown conversion
+ html = text.to_s
+ # Convert *text* to text
+ html = html.gsub(/\*\*?(.*?)\*\*?/) { |m| m.start_with?('**') ? "#{::Regexp.last_match(1)}" : "#{::Regexp.last_match(1)}" }
+ # Convert _text_ to text
+ html.gsub(/_(.*?)_/, '\1')
+ end
+
+ def extract_email(from_field)
+ return '' if from_field.blank?
+
+ if from_field =~ /<(.*)>/
+ ::Regexp.last_match(1)
+ else
+ from_field
+ end
+ end
+
+ def forwarded_message
+ @forwarded_message ||= Message.find_by(id: @message_id)
+ end
+
+ def email_data
+ @email_data ||= forwarded_message&.content_attributes&.dig('email')
+ end
+
+ def basic_attributes
+ { content_attributes: { forwarded_message_id: @message_id, is_forwarded_message: true } }
+ end
+
+ def formatted_info
+ {
+ from: format_from_field,
+ date: format_date_field,
+ subject: email_data['subject'] || '',
+ to: email_data['to']&.first || ''
+ }
+ end
+
+ def format_from_field
+ from_field = extract_from_field
+ parse_from_field(from_field)
+ 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 =~ /(.*)<(.*)>/
+ name = ::Regexp.last_match(1).strip
+ email = ::Regexp.last_match(2).strip
+ "#{name} <#{email}>"
+ else
+ from_field
+ end
+ end
+
+ def format_date_field
+ date_str = extract_date_string
+ format_date_string(date_str)
+ end
+
+ def extract_date_string
+ email_data['date'] || ''
+ end
+
+ def format_date_string(date_str)
+ return '' if date_str.blank?
+
+ parsed_date = parse_date(date_str)
+
+ if parsed_date
+ parsed_date.strftime('%a, %b %d, %Y at %l:%M %p')
+ else
+ date_str
+ end
+ end
+
+ def parse_date(date_str)
+ return nil if date_str.blank?
+
+ DateTime.parse(date_str)
+ rescue StandardError
+ nil
+ end
+end
diff --git a/app/builders/messages/message_builder.rb b/app/builders/messages/message_builder.rb
index e1087b19f..c0ef6803b 100644
--- a/app/builders/messages/message_builder.rb
+++ b/app/builders/messages/message_builder.rb
@@ -9,14 +9,11 @@ class Messages::MessageBuilder
@user = user
@message_type = params[:message_type] || 'outgoing'
@attachments = params[:attachments]
- @automation_rule = content_attributes&.dig(:automation_rule_id)
- return unless params.instance_of?(ActionController::Parameters)
-
- @in_reply_to = content_attributes&.dig(:in_reply_to)
- @items = content_attributes&.dig(:items)
+ process_content_attributes
end
def perform
+ process_forwarded_message if @forwarded_message_id.present?
@message = @conversation.messages.build(message_params)
process_attachments
process_emails
@@ -26,6 +23,21 @@ class Messages::MessageBuilder
private
+ def process_forwarded_message
+ builder = Messages::ForwardedMessageBuilder.new(@forwarded_message_id)
+ @forwarded_attributes = builder.perform
+
+ # Update content to include forwarded message
+ original_content = @params[:content_original] || @params[:content]
+ @params[:content] = builder.formatted_content(@params[:content])
+
+ # Update email data
+ return unless @forwarded_attributes[:content_attributes] && @conversation.inbox&.inbox_type == 'Email'
+
+ # Ensure we have valid email data structure to avoid breaking the rendering
+ @forwarded_attributes[:content_attributes][:email] = builder.forwarded_email_data(original_content)
+ end
+
# Extracts content attributes from the given params.
# - Converts ActionController::Parameters to a regular hash if needed.
# - Attempts to parse a JSON string if content is a string.
@@ -58,6 +70,18 @@ class Messages::MessageBuilder
{}
end
+ def process_content_attributes
+ @automation_rule = content_attributes&.dig(:automation_rule_id)
+ return unless @params.instance_of?(ActionController::Parameters)
+
+ @forwarded_message_id = content_attributes&.dig(:forwarded_message_id)
+ @in_reply_to = content_attributes&.dig(:in_reply_to)
+ @items = content_attributes&.dig(:items)
+
+ # Store original content before any modifications
+ @params[:content_original] = @params[:content].dup if @params[:content].present?
+ end
+
def process_attachments
return if @attachments.blank?
@@ -151,6 +175,11 @@ class Messages::MessageBuilder
in_reply_to: @in_reply_to,
echo_id: @params[:echo_id],
source_id: @params[:source_id]
- }.merge(external_created_at).merge(automation_rule_id).merge(campaign_id).merge(template_params)
+ }
+ .merge(external_created_at)
+ .merge(automation_rule_id)
+ .merge(campaign_id)
+ .merge(template_params)
+ .merge(@forwarded_attributes || {})
end
end
diff --git a/app/javascript/dashboard/components-next/message/MessageList.vue b/app/javascript/dashboard/components-next/message/MessageList.vue
index 44b317c56..4f219b079 100644
--- a/app/javascript/dashboard/components-next/message/MessageList.vue
+++ b/app/javascript/dashboard/components-next/message/MessageList.vue
@@ -95,6 +95,16 @@ const getInReplyToMessage = parentMessage => {
return replyMessage ? useCamelCase(replyMessage) : null;
};
+
+/**
+ * Gets the address of the forwarded message
+ * @param {Object} message - The message containing the forwarded message reference
+ * @returns {Array|null} - The email addresses of the forwarded message, or null if not forwarded
+ */
+const getForwardedMessageAddress = message => {
+ const { forwardedMessageId, toEmails } = message.contentAttributes || {};
+ return forwardedMessageId ? toEmails : null;
+};
@@ -105,6 +115,11 @@ const getInReplyToMessage = parentMessage => {
v-if="firstUnreadId && message.id === firstUnreadId"
name="unreadBadge"
/>
+ Attachments: