Class: Goodmail::Builder

Inherits:
Object
  • Object
show all
Includes:
ERB::Util
Defined in:
lib/goodmail/builder.rb

Overview

Builds the HTML content string based on DSL method calls.

Constant Summary collapse

HTML_SANITIZER =

Initialize a basic sanitizer allowing only <a> tags with href

Rails::Html::SafeListSanitizer.new
ALLOWED_TAGS =
%w(a).freeze
ALLOWED_ATTRIBUTES =
%w(href).freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeBuilder

Returns a new instance of Builder.



20
21
22
# File 'lib/goodmail/builder.rb', line 20

def initialize
  @parts = []
end

Instance Attribute Details

#partsObject

Returns the value of attribute parts.



18
19
20
# File 'lib/goodmail/builder.rb', line 18

def parts
  @parts
end

Instance Method Details

#button(text, url) ⇒ Object



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/goodmail/builder.rb', line 39

def button(text, url)
  # Standard HTML button link
  button_html = %(<a href="#{h url}" class="goodmail-button-link" style="color:#ffffff;">#{h text}</a>)
  # VML fallback for Outlook
  vml_button = <<~VML
    <v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" href="#{h url}" style="height:44px; v-text-anchor:middle; width:200px;" arcsize="10%" stroke="f" fillcolor="#{Goodmail.config.brand_color}">
      <w:anchorlock/>
      <center style="color:#ffffff; font-family:sans-serif; font-size:14px; font-weight:bold;">
        #{h text}
      </center>
    </v:roundrect>
  VML
  # MSO conditional wrapper
  mso_wrapper = <<~MSO
    <!--[if mso]>
    <table width="100%" cellpadding="0" cellspacing="0" border="0" style="border-spacing: 0; border-collapse: collapse; mso-table-lspace:0pt; mso-table-rspace:0pt;"><tr><td style="padding: 10px 0;" align="center">
    #{vml_button.strip}
    </td></tr></table>
    <![endif]-->
    <!--[if !mso]><!-->
    #{button_html}
    <!--<![endif]-->
  MSO
  # Final container div with class for primary CSS styling
  parts << %(<div class="goodmail-button" style="text-align: center; margin: 24px 0;">#{mso_wrapper.strip.html_safe}</div>)
end

#center(&block) ⇒ Object



122
123
124
# File 'lib/goodmail/builder.rb', line 122

def center(&block)
  wrap("div", "text-align:center;", &block)
end

#code_box(text) ⇒ Object

Adds a simple code box with background styling.



93
94
95
96
# File 'lib/goodmail/builder.rb', line 93

def code_box(text)
  # Re-added background/padding; content is simple, should survive Premailer plain text.
  parts << %(<p style="background:#F8F8F8; padding:20px; font-style:italic; text-align:center; color:#404040; margin:16px 0; border-radius: 4px;"><strong>#{h text}</strong></p>)
end

#html(raw_html_string) ⇒ Object

Allows inserting raw, trusted HTML. Use with extreme caution.



132
133
134
# File 'lib/goodmail/builder.rb', line 132

def html(raw_html_string)
  parts << raw_html_string.to_s
end

#html_outputObject

Returns the collected HTML parts joined together.



137
138
139
# File 'lib/goodmail/builder.rb', line 137

def html_output
  parts.join("\n")
end

#image(src, alt = "", width: nil, height: nil) ⇒ Object



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/goodmail/builder.rb', line 66

def image(src, alt = "", width: nil, height: nil)
  alt_text = alt.present? ? alt : Goodmail.config.company_name # Default alt text
  style = "max-width:100%; height:auto; display: block; margin: 0 auto;"
  style += " width:#{width}px;" if width
  style += " height:#{height}px;" if height
  # Standard image tag
  img_tag = %(<img class="goodmail-image" src="#{h src}" alt="#{h alt_text}" style="#{style}">)
  # MSO conditional wrapper for centering
  mso_wrapper = <<~MSO
    <!--[if mso]>
    <table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="border-spacing:0; border-collapse:collapse; mso-table-lspace:0pt; mso-table-rspace:0pt;"><tr><td style="padding: 20px 0;" align="center">
    <![endif]-->
    #{img_tag}
    <!--[if mso]>
    </td></tr></table>
    <![endif]-->
  MSO
  parts << mso_wrapper.strip.html_safe
end

#lineObject



126
127
128
129
# File 'lib/goodmail/builder.rb', line 126

def line
  # Use a class for easier styling via layout CSS
  parts << %(<hr class="goodmail-hr">)
end

#price_row(name, price) ⇒ Object

Adds a simple price row as a styled paragraph. NOTE: This does not create a table structure.



88
89
90
# File 'lib/goodmail/builder.rb', line 88

def price_row(name, price)
  parts << %(<p style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; font-size: 14px; font-weight:bold; text-align:center; border-top:1px solid #eaeaea; padding:14px 0; margin: 0;">#{h name} &nbsp; &ndash; &nbsp; #{h price}</p>)
end

#sign(name = Goodmail.config.company_name) ⇒ Object



103
104
105
106
# File 'lib/goodmail/builder.rb', line 103

def sign(name = Goodmail.config.company_name)
  # Use #777 for better contrast than #888
  parts << %(<p style="margin:16px 0; line-height: 1.6;"><span style="color: #777;">– #{h name}</span></p>)
end

#space(px = 16) ⇒ Object



98
99
100
101
# File 'lib/goodmail/builder.rb', line 98

def space(px = 16)
  # Rely on CSS height for spacing, avoid &nbsp; if possible
  parts << %(<div style="height:#{Integer(px)}px; line-height: #{Integer(px)}px; font-size: 1px;"></div>)
end

#text(str) ⇒ Object

Adds a paragraph of text. Handles newline characters for
tags. Allows safe inline <a> tags with href attributes; strips other HTML.



28
29
30
31
32
33
34
35
36
37
# File 'lib/goodmail/builder.rb', line 28

def text(str)
  # Sanitize first, allowing only safe tags like <a>
  sanitized_content = HTML_SANITIZER.sanitize(
    str.to_s, # Ensure input is a string
    tags: ALLOWED_TAGS,
    attributes: ALLOWED_ATTRIBUTES
  )
  # Then handle newlines and wrap in paragraph
  parts << tag(:p, sanitized_content.gsub(/\n/, "<br>"), style: "margin:16px 0; line-height: 1.6;")
end