chore: Forward outgoing email message

This commit is contained in:
iamsivin
2025-05-01 17:12:05 +05:30
parent f4ca2529ba
commit d1ac7211ec
4 changed files with 236 additions and 167 deletions
+233 -158
View File
@@ -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", '<br>')
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?('**') ? "<b>#{Regexp.last_match(1)}</b>" : "<i>#{Regexp.last_match(1)}</i>"
end
html.gsub(/_(.*?)_/, '<i>\1</i>')
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 = '<div dir="ltr">'
if content.blank?
"#{base}#{forwarded_header_html}#{forwarded_body_html}</div>"
else
"#{base}#{content}<br><br>#{forwarded_header_html}#{forwarded_body_html}</div>"
end
end
def forwarded_header_html
from_value = formatted_info[:from].to_s
email = Messages::ForwardedMessageFormatter.extract_email(from_value)
[
'<div class="gmail_quote gmail_quote_container">',
'<div dir="ltr" class="gmail_attr">---------- Forwarded message ---------<br>',
build_email_html(email),
build_field_html('Date', formatted_info[:date]),
build_field_html('Subject', formatted_info[:subject]),
build_to_html(formatted_info[:to]),
'</div><br><br>'
].compact.join
end
def build_email_html(email)
return nil if email.blank?
"From: &lt;<a href=\"mailto:#{email}\">#{email}</a>&gt;<br>"
end
def build_field_html(label, value)
value.present? ? "#{label}: #{value}<br>" : nil
end
def build_to_html(to_email)
return nil if to_email.blank?
"To: &lt;<a href=\"mailto:#{to_email}\">#{to_email}</a>&gt;<br>"
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 = '<div dir="ltr">'
if content.blank?
"#{base}#{forwarded_header_html}#{forwarded_body_html}</div>"
else
"#{base}#{content}<br><br>#{forwarded_header_html}#{forwarded_body_html}</div>"
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
[
'<div class="gmail_quote gmail_quote_container">',
'<div dir="ltr" class="gmail_attr">---------- Forwarded message ---------<br>',
"From: <strong class=\"gmail_sendername\" dir=\"auto\">#{name}</strong> ",
"<span dir=\"auto\">&lt;<a href=\"mailto:#{email}\">#{email}</a>&gt;</span><br>",
"Date: #{formatted_info[:date]}<br>",
"Subject: #{formatted_info[:subject]}<br>",
"To: &lt;<a href=\"mailto:#{formatted_info[:to]}\">#{formatted_info[:to]}</a>&gt;<br>",
'</div><br><br>'
].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?
"<pre>#{ERB::Util.html_escape(email_data.dig('text_content', 'full'))}</pre>"
else
"<pre>#{ERB::Util.html_escape(forwarded_message.content.to_s)}</pre>"
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?('**') ? "<b>#{::Regexp.last_match(1)}</b>" : "<i>#{::Regexp.last_match(1)}</i>"
end
html.gsub(/_(.*?)_/, '<i>\1</i>')
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
@@ -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 (
@@ -39,15 +39,9 @@ const showQuotedMessage = ref(false);
/>
<div class="px-4 pb-4 flex flex-col gap-2">
<div class="flex items-center gap-1.5">
<div
class="h-px w-20 bg-n-alpha-2 border-t border-dashed border-n-slate-12"
/>
<span class="font-semibold text-sm text-n-slate-12">
<span class="text-sm text-n-slate-12">
{{ t('FORWARD_MESSAGE_FORM.FORWARDED_MESSAGE') }}
</span>
<div
class="h-px w-20 bg-n-alpha-2 border-t border-dashed border-n-slate-12"
/>
</div>
<EmailMeta />
</div>
@@ -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": {