Module: CommunicationsHelper
- Defined in:
- app/helpers/communications_helper.rb
Overview
View helper: communications.
Instance Method Summary collapse
-
#attachment_name(upload) ⇒ String
Display name for an upload attached to a communication.
-
#clean_received_body(body) ⇒ String
Strips quoted/forwarded content from a plain-text received email body for display.
-
#combo_category_select_options(category = nil) ⇒ Array<Array(String, String)>
Options for the recipient category combo select: email TO/CC/BCC and fax, narrowed to a single category when one is given.
-
#communication_actions(c) ⇒ Array<String>
Action links shown on a communication's detail page, gated by the current user's abilities and the communication's state machine events.
-
#communication_recipient_delete_button(f) ⇒ String
Renders the delete affordance for a nested communication-recipient field: a
_destroycheckbox set for persisted rows, a JS remove link for new ones. -
#communication_recipient_state_label(communication_recipient) ⇒ ActiveSupport::SafeBuffer
Bootstrap badge for a recipient's delivery state (delivered, bounced, …).
-
#communication_state_details(cr) ⇒ ActiveSupport::SafeBuffer
Human-readable summary of when a recipient's delivery state last changed, plus any gateway response message recorded with it.
-
#communication_state_label(communication) ⇒ ActiveSupport::SafeBuffer
Bootstrap badge for a communication's state; appends "(Scheduled)" when a queued communication has a future transmit time.
-
#email_links_in_new_tab(html) ⇒ String
Rewrites external (http/https) links in a displayed email body so they open in a new browser tab instead of "in place".
-
#email_template_option(template) ⇒ Array(String, Integer)
Builds a single
[label, id]option pair for an email template select. -
#email_templates_for_user(include_email_template_id) ⇒ Hash{String => Array<Array(String, Integer)>}
Builds the grouped option list of email templates available to the current context user: templates private to that employee plus global templates.
-
#preview_communication_body(communication) ⇒ String?
Renders the body preview partial for a communication.
-
#setup_communication(communication) ⇒ Communication
Ensures the communication has at least one email recipient row so the recipient sub-form renders an editable entry.
-
#translate_cp_category_to_combo(category) ⇒ String?
Maps a contact-point category to the matching combo-select value.
Instance Method Details
#attachment_name(upload) ⇒ String
Display name for an upload attached to a communication.
19 20 21 |
# File 'app/helpers/communications_helper.rb', line 19 def (upload) upload. end |
#clean_received_body(body) ⇒ String
Strips quoted/forwarded content from a plain-text received email body for display.
Handles two common patterns:
- "On , wrote:" reply blocks (via EmailReplyParser)
- "From: / To: / Date: / Subject:" forwarded message header blocks (via regex)
228 229 230 231 232 |
# File 'app/helpers/communications_helper.rb', line 228 def clean_received_body(body) text = EmailReplyParser.parse_reply(body.to_s) # Strip forwarded-message header blocks: "From:\nTo: ..." and everything after text.sub(/\n+\s*From:[^\n]*\n[\t ]*(?:To|Sent|Date|Cc|Subject):.*\z/m, '').strip end |
#combo_category_select_options(category = nil) ⇒ Array<Array(String, String)>
Options for the recipient category combo select: email TO/CC/BCC and fax,
narrowed to a single category when one is given.
125 126 127 128 129 130 131 132 133 134 135 136 |
# File 'app/helpers/communications_helper.rb', line 125 def (category = nil) opts = [] if category.nil? || (category == 'email') opts += [ ['Email TO', 'email_to'], ['Email CC', 'email_cc'], ['Email BCC', 'email_bcc'] ] end opts += [%w[Fax fax]] if category.nil? || (category == 'fax') opts end |
#communication_actions(c) ⇒ Array<String>
Action links shown on a communication's detail page, gated by the current
user's abilities and the communication's state machine events.
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
# File 'app/helpers/communications_helper.rb', line 93 def communication_actions(c) [].tap do |cmds| cmds << c.human_state_name.titleize cmds << link_to(fa_icon('pen-to-square', text: 'Edit'), edit_communication_path(@communication)) if (c.draft? || c.exception?) && can?(:update, c) if can?(:destroy, c) && !c.sent? cmds << link_to(fa_icon('trash', text: 'Delete'), communication_path(@communication), data: { turbo_confirm: 'Delete for sure?', turbo_method: :delete }) end cmds << link_to(fa_icon('repeat', text: 'Resend this email'), resend_email_communication_path(@communication)) if can?(:create, Communication) && !@communication.draft? && @communication.direction_outbound? cmds << link_to(fa_icon('copy', text: 'Copy into new blank communication'), copy_communication_path(@communication)) if can?(:create, Communication) && !@communication.draft? if @communication.can_queue? cmds << link_to(fa_icon('envelope', text: 'Queue for delivery'), communication_path(@communication, communication: { state_event: :queue }), data: { turbo_method: :put }) end cmds << link_to(fa_icon('arrow-rotate-left', text: 'Back to Draft'), communication_path(@communication, communication: { state_event: :unqueue }), data: { turbo_method: :put }) if @communication.can_unqueue? cmds << link_to(fa_icon('envelope', text: 'Send Now'), send_now_communication_path(@communication), data: { turbo_method: :put }) if @communication.draft? || @communication.queued? if @communication.can_resend? cmds << link_to(fa_icon('envelope', text: 'Queue to Resend'), communication_path(@communication, communication: { state_event: :resend }), data: { turbo_method: :put }) end cmds << link_to(fa_icon('reply', text: 'Reply'), reply_communication_path(@communication)) cmds << link_to(fa_icon('reply-all', text: 'Reply All'), reply_all_communication_path(@communication)) if @communication.communication_recipients.many? cmds << link_to(fa_icon('bell', text: 'Events'), events_communication_path(@communication)) end end |
#communication_recipient_delete_button(f) ⇒ String
Renders the delete affordance for a nested communication-recipient field:
a _destroy checkbox set for persisted rows, a JS remove link for new ones.
64 65 66 67 68 69 70 71 72 73 74 |
# File 'app/helpers/communications_helper.rb', line 64 def (f) if f.object.persisted? content_tag(:div, class: 'btn btn-outline-warning') do concat f.hidden_field '_destroy' concat f.label '_destroy', fa_icon('trash'), class: 'm-0 p-0' concat f.check_box '_destroy', class: 'destroy' end else link_to(fa_icon('trash'), '#', class: 'btn btn-outline-warning trash-remove', data: { 'class-to-remove': '.communication_recipient' }) end end |
#communication_recipient_state_label(communication_recipient) ⇒ ActiveSupport::SafeBuffer
Bootstrap badge for a recipient's delivery state (delivered, bounced, …).
166 167 168 169 170 171 |
# File 'app/helpers/communications_helper.rb', line 166 def communication_recipient_state_label(communication_recipient) label_class = { processed: 'secondary', dropped: 'danger', delivered: 'success', deferred: 'warning', bounced: 'danger', opened: 'success', ok: 'success', clicked: 'success', spammed: 'danger', unsubscribed: 'warning' }[communication_recipient.state.to_sym] || 'default' content_tag :span, communication_recipient.human_state_name.upcase, class: "badge bg-#{label_class}" end |
#communication_state_details(cr) ⇒ ActiveSupport::SafeBuffer
Human-readable summary of when a recipient's delivery state last changed,
plus any gateway response message recorded with it.
81 82 83 84 85 86 |
# File 'app/helpers/communications_helper.rb', line 81 def communication_state_details(cr) msg = [] msg << "State triggered at #{cr.state_updated_at.to_fs(:crm_default)}" if cr.state_updated_at msg << "Response: #{cr.state_response}" if cr.state_response.present? raw(msg.join('. ')) end |
#communication_state_label(communication) ⇒ ActiveSupport::SafeBuffer
Bootstrap badge for a communication's state; appends "(Scheduled)" when a
queued communication has a future transmit time.
155 156 157 158 159 160 |
# File 'app/helpers/communications_helper.rb', line 155 def communication_state_label(communication) label_class = { exception: 'danger', suppressed: 'danger', queued: 'warning', sent: 'success', received: 'info', draft: 'secondary' }[communication.state.to_sym] || 'default' comm_label = communication.human_state_name.upcase comm_label << ' (Scheduled)' if communication.transmit_at && communication.queued? content_tag :span, comm_label, class: "badge bg-#{label_class}" end |
#email_links_in_new_tab(html) ⇒ String
Rewrites external (http/https) links in a displayed email body so they open
in a new browser tab instead of "in place". Display-only — never apply this to
the stored body, since Communication#compliant_body is the literal payload
handed to CommunicationMailer and preview affordances must not leak into sent
mail.
Outbound/template bodies render inline inside the Activities <turbo-frame>,
where Turbo otherwise intercepts a bare <a> click and loads the destination
into the frame. An explicit target="_blank" sidesteps that (Turbo leaves
links with an explicit target alone) and rel="noopener noreferrer" is the
standard safety companion. Received HTML emails are handled separately via a
<base target> in their srcdoc iframe (see _preview_body.html.erb).
Returns the body unchanged when it has no external anchors, and parses
fragments as fragments so a plain-text or partial body is never wrapped in a
synthetic <html>/<body>.
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 |
# File 'app/helpers/communications_helper.rb', line 202 def email_links_in_new_tab(html) source = html.to_s return source unless source.match?(/<a[\s>]/i) full_document = source.match?(/<html[\s>]|<!doctype\s+html/i) doc = full_document ? Nokogiri::HTML(source) : Nokogiri::HTML::DocumentFragment.parse(source) rewrote = false doc.css('a[href]').each do |node| next unless node['href'].to_s.match?(%r{\Ahttps?://}i) node['target'] = '_blank' node['rel'] = (node['rel'].to_s.split + %w[noopener noreferrer]).uniq.join(' ') rewrote = true end rewrote ? doc.to_html : source end |
#email_template_option(template) ⇒ Array(String, Integer)
Builds a single [label, id] option pair for an email template select.
Legacy Redactor 3 templates are flagged (v3) in the label.
49 50 51 52 53 54 55 56 57 |
# File 'app/helpers/communications_helper.rb', line 49 def email_template_option(template) # All templates are selectable, but mark v3 templates in the label label = if template.redactor_4_ready? "#{template.description} [#{template.category}]" else "#{template.description} [#{template.category}] (v3)" end [label, template.id] end |
#email_templates_for_user(include_email_template_id) ⇒ Hash{String => Array<Array(String, Integer)>}
Builds the grouped option list of email templates available to the current
context user: templates private to that employee plus global templates.
31 32 33 34 35 36 37 38 39 40 41 42 |
# File 'app/helpers/communications_helper.rb', line 31 def email_templates_for_user(include_email_template_id) = {} # Show all templates - Redactor 3 templates are shown but disabled (only v4 can be selected) base_query = EmailTemplate.non_campaign.active.order(:description) person_filter_sql = "resource_type = 'Employee' and resource_id = :employee_id" person_filter_sql = "(#{person_filter_sql}) OR email_templates.id = :email_template_id" if include_email_template_id tpl_pers = base_query.where(person_filter_sql, employee_id: @context_user.id, email_template_id: include_email_template_id) tpl_global = base_query.where(resource_id: nil) ['Private Templates'] = tpl_pers.map { |t| email_template_option(t) } if tpl_pers.present? ['Global Templates'] = tpl_global.map { |t| email_template_option(t) } if tpl_global.present? end |
#preview_communication_body(communication) ⇒ String?
Renders the body preview partial for a communication.
177 178 179 180 181 |
# File 'app/helpers/communications_helper.rb', line 177 def preview_communication_body(communication) return unless communication render partial: '/communications/preview_body', locals: { communication: communication } end |
#setup_communication(communication) ⇒ Communication
Ensures the communication has at least one email recipient row so the
recipient sub-form renders an editable entry.
9 10 11 12 13 |
# File 'app/helpers/communications_helper.rb', line 9 def setup_communication(communication) communication.tap do |c| c.communication_recipients.build(category: ContactPoint::EMAIL, email_method: 'to') unless c.communication_recipients.find { |cr| cr.category == ContactPoint::EMAIL } end end |
#translate_cp_category_to_combo(category) ⇒ String?
Maps a contact-point category to the matching combo-select value.
143 144 145 146 147 148 |
# File 'app/helpers/communications_helper.rb', line 143 def translate_cp_category_to_combo(category) { 'email' => 'email_to', 'fax' => 'fax' }[category] end |