Initial commit: Add logistics and order_detail message types
Some checks failed
Lock Threads / action (push) Has been cancelled
Mark stale issues and pull requests / stale (push) Has been cancelled
Publish Chatwoot EE docker images / build (linux/amd64, ubuntu-latest) (push) Has been cancelled
Publish Chatwoot EE docker images / build (linux/arm64, ubuntu-22.04-arm) (push) Has been cancelled
Publish Chatwoot EE docker images / merge (push) Has been cancelled
Publish Chatwoot CE docker images / build (linux/amd64, ubuntu-latest) (push) Has been cancelled
Publish Chatwoot CE docker images / build (linux/arm64, ubuntu-22.04-arm) (push) Has been cancelled
Publish Chatwoot CE docker images / merge (push) Has been cancelled
Run Chatwoot CE spec / lint-backend (push) Has been cancelled
Run Chatwoot CE spec / lint-frontend (push) Has been cancelled
Run Chatwoot CE spec / frontend-tests (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (0, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (1, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (10, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (11, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (12, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (13, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (14, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (15, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (2, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (3, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (4, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (5, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (6, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (7, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (8, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (9, 16) (push) Has been cancelled
Run Linux nightly installer / nightly (push) Has been cancelled

- Add Logistics component with progress tracking
- Add OrderDetail component for order information
- Support data-driven steps and actions
- Add blue color scale to widget SCSS
- Fix node overflow and progress bar rendering issues
- Add English translations for dashboard components

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Liang XJ
2026-01-26 11:16:56 +08:00
commit 092fb2e083
7646 changed files with 975643 additions and 0 deletions

View File

@@ -0,0 +1,214 @@
class MailPresenter < SimpleDelegator
attr_accessor :mail
def initialize(mail, account = nil)
super(mail)
@mail = mail
@account = account
end
def subject
encode_to_unicode(@mail.subject)
end
# encode decoded mail text_part or html_part if mail is multipart email
# encode decoded mail raw bodyt if mail is not multipart email but the body content is text/html
def mail_content(mail_part)
if multipart_mail_body?
decoded_multipart_mail(mail_part)
else
text_html_mail(mail_part)
end
end
# encodes mail if mail.parts is present
# encodes mail content type is multipart
def decoded_multipart_mail(mail_part)
encoded = encode_to_unicode(mail_part&.decoded)
encoded if text_mail_body? || html_mail_body?
end
# encodes mail raw body if mail.parts is empty
# encodes mail raw body if mail.content_type is plain/text
# encodes mail raw body if mail.content_type is html/text
def text_html_mail(mail_part)
decoded = mail_part&.decoded || @mail.decoded
encoded = encode_to_unicode(decoded)
encoded if html_mail_body? || text_mail_body?
end
def text_content
@decoded_text_content = mail_content(text_part) || ''
encoding = @decoded_text_content.encoding
body = EmailReplyTrimmer.trim(@decoded_text_content)
return {} if @decoded_text_content.blank? || !text_mail_body?
@text_content ||= {
full: mail_content(text_part),
reply: @decoded_text_content,
quoted: body.force_encoding(encoding).encode('UTF-8')
}
end
def html_content
encoded = mail_content(html_part) || ''
@decoded_html_content = ::HtmlParser.parse_reply(encoded)
return {} if @decoded_html_content.blank? || !html_mail_body?
body = EmailReplyTrimmer.trim(@decoded_html_content)
@html_content ||= {
full: mail_content(html_part),
reply: @decoded_html_content,
quoted: body
}
end
# check content disposition check
# if inline, upload to AWS and and take the URL
def attachments
# ref : https://github.com/gorails-screencasts/action-mailbox-action-text/blob/master/app/mailboxes/posts_mailbox.rb
mail.attachments.map do |attachment|
blob = ActiveStorage::Blob.create_and_upload!(
io: StringIO.new(attachment.body.to_s),
filename: attachment.filename.presence || "attachment_#{SecureRandom.hex(4)}",
content_type: attachment.content_type
)
{ original: attachment, blob: blob }
end
end
def number_of_attachments
mail.attachments.count
end
def serialized_data
{
bcc: bcc,
cc: cc,
content_type: content_type,
date: date,
from: from,
headers: headers_data,
html_content: html_content,
in_reply_to: in_reply_to,
message_id: message_id,
multipart: multipart?,
number_of_attachments: number_of_attachments,
references: references,
subject: subject,
text_content: text_content,
to: to,
auto_reply: auto_reply?
}
end
def in_reply_to
return if @mail.in_reply_to.blank?
# Although the "in_reply_to" field in the email can potentially hold multiple values,
# our current system does not have the capability to handle this.
# FIX ME: Address this issue by returning the complete results and utilizing them for querying conversations.
@mail.in_reply_to.is_a?(Array) ? @mail.in_reply_to.first : @mail.in_reply_to
end
def references
return [] if @mail.references.blank?
Array.wrap(@mail.references)
end
def from
# changing to downcase to avoid case mismatch while finding contact
Array.wrap(@mail.reply_to.presence || @mail.from).map(&:downcase)
end
def sender_name
Mail::Address.new((@mail[:reply_to] || @mail[:from]).value).name
end
def original_sender
from_email_address(@mail[:reply_to].try(:value)) || @mail['X-Original-Sender'].try(:value) || from_email_address(from.first)
end
def headers_data
headers = {
'x-original-from' => @mail['X-Original-From']&.value,
'x-original-sender' => @mail['X-Original-Sender']&.value,
'x-forwarded-for' => @mail['X-Forwarded-For']&.value
}.compact
headers.presence
end
def from_email_address(email)
Mail::Address.new(email).address
end
def email_forwarded_for
@mail['X-Forwarded-For'].try(:value)
end
def mail_receiver
if @mail.to.blank?
return [email_forwarded_for] if email_forwarded_for.present?
[]
else
@mail.to
end
end
def auto_reply?
auto_submitted? || x_auto_reply?
end
def bounced?
@mail.bounced? || @mail['X-Failed-Recipients'].try(:value).present?
end
def notification_email_from_chatwoot?
# notification emails are send via mailer sender email address. so it should match
original_sender == Mail::Address.new(ENV.fetch('MAILER_SENDER_EMAIL', 'Chatwoot <accounts@chatwoot.com>')).address
end
private
def auto_submitted?
@mail['Auto-Submitted'].present? && @mail['Auto-Submitted'].value != 'no'
end
def x_auto_reply?
@mail['X-Autoreply'].present? && @mail['X-Autoreply'].value == 'yes'
end
# forcing the encoding of the content to UTF-8 so as to be compatible with database and serializers
def encode_to_unicode(str)
return '' if str.blank?
current_encoding = str.encoding.name
return str if current_encoding == 'UTF-8'
str.encode(current_encoding, 'UTF-8', invalid: :replace, undef: :replace, replace: '?')
rescue StandardError
''
end
def html_mail_body?
((mail.content_type || '').include? 'text/html') || @mail.html_part&.content_type&.include?('text/html')
end
def text_mail_body?
((mail.content_type || '').include? 'text/plain') || @mail.text_part&.content_type&.include?('text/plain')
end
def multipart_mail_body?
((mail.content_type || '').include? 'multipart') || @mail.parts.any?
end
end