mail_style 0.1.4 → 0.1.5
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/mail_style/inline_styles.rb +75 -39
- data/lib/mail_style/sass_support.rb +5 -3
- data/readme.textile +22 -4
- data/spec/inline_styles_spec.rb +113 -39
- data/spec/spec_helper.rb +6 -1
- metadata +2 -2
@@ -5,21 +5,29 @@ require 'css_parser'
|
|
5
5
|
module MailStyle
|
6
6
|
module InlineStyles
|
7
7
|
DOCTYPE = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
|
8
|
-
|
8
|
+
|
9
9
|
module InstanceMethods
|
10
10
|
def create_mail_with_inline_styles
|
11
|
-
write_inline_styles
|
11
|
+
write_inline_styles
|
12
12
|
create_mail_without_inline_styles
|
13
13
|
end
|
14
|
-
|
14
|
+
|
15
15
|
protected
|
16
|
-
|
16
|
+
|
17
|
+
# Flatten nested parts
|
18
|
+
def collect_parts(parts)
|
19
|
+
nested = parts.present? ? parts.map { |p| collect_parts(p.parts) }.flatten : []
|
20
|
+
[parts, nested].flatten
|
21
|
+
end
|
22
|
+
|
17
23
|
def write_inline_styles
|
24
|
+
parts = collect_parts(@parts)
|
25
|
+
|
18
26
|
# Parse only text/html parts
|
19
27
|
parsable_parts(@parts).each do |part|
|
20
28
|
part.body = parse_html(part.body)
|
21
29
|
end
|
22
|
-
|
30
|
+
|
23
31
|
# Parse single part emails if the body is html
|
24
32
|
real_content_type, ctype_attrs = parse_content_type
|
25
33
|
self.body = parse_html(body) if body.is_a?(String) && real_content_type == 'text/html'
|
@@ -27,9 +35,9 @@ module MailStyle
|
|
27
35
|
|
28
36
|
def parsable_parts(parts)
|
29
37
|
selected = []
|
30
|
-
parts.each do |
|
31
|
-
selected <<
|
32
|
-
selected += parsable_parts(
|
38
|
+
parts.each do |part|
|
39
|
+
selected << part if part.content_type == 'text/html'
|
40
|
+
selected += parsable_parts(part.parts)
|
33
41
|
end
|
34
42
|
selected
|
35
43
|
end
|
@@ -41,17 +49,18 @@ module MailStyle
|
|
41
49
|
|
42
50
|
# Write inline styles
|
43
51
|
element_styles = {}
|
44
|
-
|
52
|
+
|
45
53
|
css_parser.each_selector do |selector, declaration, specificity|
|
54
|
+
next if selector.include?(':')
|
46
55
|
html_document.css(selector).each do |element|
|
47
56
|
declaration.to_s.split(';').each do |style|
|
48
57
|
# Split style in attribute and value
|
49
58
|
attribute, value = style.split(':').map(&:strip)
|
50
|
-
|
59
|
+
|
51
60
|
# Set element style defaults
|
52
61
|
element_styles[element] ||= {}
|
53
62
|
element_styles[element][attribute] ||= { :specificity => 0, :value => '' }
|
54
|
-
|
63
|
+
|
55
64
|
# Update attribute value if specificity is higher than previous values
|
56
65
|
if element_styles[element][attribute][:specificity] <= specificity
|
57
66
|
element_styles[element][attribute] = { :specificity => specificity, :value => value }
|
@@ -59,12 +68,12 @@ module MailStyle
|
|
59
68
|
end
|
60
69
|
end
|
61
70
|
end
|
62
|
-
|
71
|
+
|
63
72
|
# Loop through element styles
|
64
73
|
element_styles.each_pair do |element, attributes|
|
65
74
|
# Elements current styles
|
66
75
|
current_style = element['style'].to_s.split(';').sort
|
67
|
-
|
76
|
+
|
68
77
|
# Elements new styles
|
69
78
|
new_style = attributes.map{|attribute, style| "#{attribute}: #{update_image_urls(style[:value])}"}
|
70
79
|
|
@@ -74,94 +83,121 @@ module MailStyle
|
|
74
83
|
# Set new styles
|
75
84
|
element['style'] = style.join(';')
|
76
85
|
end
|
77
|
-
|
86
|
+
|
78
87
|
# Return HTML
|
79
88
|
html_document.to_html
|
80
89
|
end
|
81
|
-
|
90
|
+
|
82
91
|
def absolutize_image_sources(document)
|
83
92
|
document.css('img').each do |img|
|
84
93
|
src = img['src']
|
85
94
|
img['src'] = src.gsub(src, absolutize_url(src))
|
86
95
|
end
|
87
|
-
|
96
|
+
|
88
97
|
document
|
89
98
|
end
|
90
|
-
|
99
|
+
|
91
100
|
# Create Nokogiri html document from part contents and add/amend certain elements.
|
92
101
|
# Reference: http://www.creativeglo.co.uk/email-design/html-email-design-and-coding-tips-part-2/
|
93
102
|
def create_html_document(body)
|
94
103
|
# Add doctype to html along with body
|
95
104
|
document = Nokogiri::HTML.parse(DOCTYPE + body)
|
96
|
-
|
105
|
+
|
97
106
|
# Set some meta stuff
|
98
107
|
html = document.at_css('html')
|
99
108
|
html['xmlns'] = 'http://www.w3.org/1999/xhtml'
|
100
|
-
|
109
|
+
|
101
110
|
# Create <head> element if missing
|
102
111
|
head = document.at_css('head')
|
103
|
-
|
112
|
+
|
104
113
|
unless head.present?
|
105
114
|
head = Nokogiri::XML::Node.new('head', document)
|
106
115
|
document.at_css('body').add_previous_sibling(head)
|
107
116
|
end
|
108
|
-
|
117
|
+
|
109
118
|
# Add utf-8 content type meta tag
|
110
119
|
meta = Nokogiri::XML::Node.new('meta', document)
|
111
120
|
meta['http-equiv'] = 'Content-Type'
|
112
121
|
meta['content'] = 'text/html; charset=utf-8'
|
113
122
|
head.add_child(meta)
|
114
|
-
|
123
|
+
|
124
|
+
# Grab all the styles that are inside <style> elements already in the document
|
125
|
+
@inline_rules = ""
|
126
|
+
document.css("style").each do |style|
|
127
|
+
# Do not inline print media styles
|
128
|
+
next if style['media'] == 'print'
|
129
|
+
|
130
|
+
# <style data-immutable="true"> are kept in the document
|
131
|
+
next if style['data-immutable'] == 'true'
|
132
|
+
|
133
|
+
@inline_rules << style.content
|
134
|
+
style.remove
|
135
|
+
end
|
136
|
+
|
115
137
|
# Return document
|
116
138
|
document
|
117
139
|
end
|
118
|
-
|
140
|
+
|
119
141
|
# Update image urls
|
120
142
|
def update_image_urls(style)
|
121
143
|
if default_url_options[:host].present?
|
122
144
|
# Replace urls in stylesheets
|
123
145
|
style.gsub!($1, absolutize_url($1, 'stylesheets')) if style[/url\(['"]?(.*)['"]?\)/i]
|
124
146
|
end
|
125
|
-
|
147
|
+
|
126
148
|
style
|
127
149
|
end
|
128
|
-
|
129
|
-
|
150
|
+
|
151
|
+
# Absolutize URL (Absolutize? Seriously?)
|
130
152
|
def absolutize_url(url, base_path = '')
|
131
153
|
original_url = url
|
132
|
-
|
154
|
+
|
133
155
|
unless original_url[URI::regexp(%w[http https])]
|
134
|
-
|
156
|
+
protocol = default_url_options[:protocol]
|
157
|
+
protocol = "http://" if protocol.blank?
|
158
|
+
protocol+= "://" unless protocol.include?("://")
|
159
|
+
|
135
160
|
host = default_url_options[:host]
|
136
|
-
|
161
|
+
|
162
|
+
[host,protocol].each{|r| original_url.gsub!(r,"") }
|
163
|
+
host = protocol+host unless host[URI::regexp(%w[http https])]
|
164
|
+
|
165
|
+
url = URI.join host, base_path, original_url
|
137
166
|
end
|
138
|
-
|
139
|
-
url
|
167
|
+
|
168
|
+
url.to_s
|
169
|
+
|
140
170
|
end
|
141
171
|
|
142
172
|
# Css Parser
|
143
173
|
def css_parser
|
144
174
|
parser = CssParser::Parser.new
|
145
|
-
|
175
|
+
|
176
|
+
parser.add_block!(css_rules) if @css.present?
|
177
|
+
parser.add_block!(@inline_rules)
|
146
178
|
parser
|
147
179
|
end
|
148
|
-
|
180
|
+
|
149
181
|
# Css Rules
|
150
182
|
def css_rules
|
151
|
-
|
183
|
+
if @css.is_a?(Array)
|
184
|
+
@css.collect{|r| File.read(css_file(r)) }.join("\n")
|
185
|
+
else
|
186
|
+
File.read css_file(@css)
|
187
|
+
end
|
152
188
|
end
|
153
|
-
|
189
|
+
|
154
190
|
# Find the css file
|
155
|
-
def css_file
|
156
|
-
if
|
157
|
-
css =
|
191
|
+
def css_file(name=nil)
|
192
|
+
if name.present?
|
193
|
+
css = name.to_s
|
158
194
|
css = css[/\.css$/] ? css : "#{css}.css"
|
159
195
|
path = File.join(RAILS_ROOT, 'public', 'stylesheets', css)
|
160
196
|
File.exist?(path) ? path : raise(CSSFileNotFound)
|
161
197
|
end
|
162
198
|
end
|
163
199
|
end
|
164
|
-
|
200
|
+
|
165
201
|
def self.included(receiver)
|
166
202
|
receiver.send :include, InstanceMethods
|
167
203
|
receiver.class_eval do
|
@@ -2,14 +2,16 @@ module MailStyle
|
|
2
2
|
module InlineStyles
|
3
3
|
module InstanceMethods
|
4
4
|
def css_file_with_sass
|
5
|
+
p "css_file_with_sass"
|
5
6
|
if !Sass::Plugin.checked_for_updates || Sass::Plugin.options[:always_update] || Sass::Plugin.options[:always_check]
|
6
7
|
Sass::Plugin.update_stylesheets
|
7
8
|
end
|
8
|
-
|
9
|
+
|
9
10
|
css_file_without_sass
|
10
11
|
end
|
11
|
-
|
12
|
-
alias_method_chain :css_file, :sass
|
12
|
+
|
13
|
+
#alias_method_chain :css_file, :sass
|
13
14
|
end
|
14
15
|
end
|
15
16
|
end
|
17
|
+
|
data/readme.textile
CHANGED
@@ -11,9 +11,9 @@ h2. Install
|
|
11
11
|
First install the dependencies:
|
12
12
|
|
13
13
|
<pre><code>sudo gem install nokogiri css_parser</code></pre>
|
14
|
-
|
14
|
+
|
15
15
|
Then install MailStyle to your rails app, either as a plugin:
|
16
|
-
|
16
|
+
|
17
17
|
<pre><code>script/plugin install http://github.com/purify/mail_style</code></pre>
|
18
18
|
|
19
19
|
Or you can install the gem:
|
@@ -36,16 +36,33 @@ Simply add the <code>css</code> method to your deliver actions:
|
|
36
36
|
<pre><code>class Notifier < ActionMailer::Base
|
37
37
|
def welcome_email
|
38
38
|
css :email
|
39
|
-
|
39
|
+
|
40
40
|
subject 'Welcome Aboard'
|
41
41
|
recipients 'someone@example.com'
|
42
42
|
from 'jimneath@googlemail.com'
|
43
43
|
sent_on Time.now
|
44
44
|
end
|
45
|
+
|
46
|
+
def newsletter_email
|
47
|
+
css [:email,:newsletter]
|
48
|
+
|
49
|
+
subject 'Newsletter'
|
50
|
+
recipients 'someone@example.com'
|
51
|
+
from 'jimneath@googlemail.com'
|
52
|
+
sent_on Time.now
|
53
|
+
end
|
45
54
|
end</code></pre>
|
46
55
|
|
47
56
|
This will look for a css file called _email.css_ in your _public/stylesheets_ folder. The <code>css</code> method can take either a string or a symbol. You can also pass the css file name with or without the .css extension.
|
48
57
|
|
58
|
+
h3. Styles in the email's HEAD
|
59
|
+
|
60
|
+
By default, <style> elements in the email document's <head> are processed and removed from the <head>.
|
61
|
+
|
62
|
+
Style elements with <code>media="print"</code> are ignored.
|
63
|
+
|
64
|
+
You can set a special <code>data-immutable="true"</code> attribute on <style> tags you do not want to be processed and removed from the document's <head>.
|
65
|
+
|
49
66
|
h2. Image URL Correcting
|
50
67
|
|
51
68
|
If you have _default_url_options[:host]_ set in your mailer, then MailStyle will do it's best to make the urls of images absolute.
|
@@ -84,4 +101,5 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
84
101
|
|
85
102
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
86
103
|
|
87
|
-
THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
104
|
+
THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
105
|
+
|
data/spec/inline_styles_spec.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# coding: utf-8
|
2
|
-
require
|
2
|
+
require 'spec_helper'
|
3
3
|
|
4
4
|
RAILS_ROOT = File.join(File.dirname(__FILE__), '../../../../')
|
5
5
|
|
@@ -15,7 +15,23 @@ class TestMailer < ActionMailer::Base
|
|
15
15
|
def test_multipart(css_file = nil)
|
16
16
|
setup_email(css_file)
|
17
17
|
content_type 'multipart/alternative'
|
18
|
-
part :content_type => 'text/html', :body => '<p class="text">Hello World</p>'
|
18
|
+
part :content_type => 'text/html', :body => '<p class="text">Hello <a href="htt://example.com/">World</a></p>'
|
19
|
+
part :content_type => 'text/plain', :body => 'Hello World'
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_nested_multipart_mixed(css_file = nil)
|
23
|
+
setup_email(css_file)
|
24
|
+
content_type "multipart/mixed"
|
25
|
+
part :content_type => "multipart/alternative", :content_disposition => "inline" do |p|
|
26
|
+
p.part :content_type => 'text/html', :body => '<p class="text">Hello World</p>'
|
27
|
+
p.part :content_type => 'text/plain', :body => 'Hello World'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_inline_rules(rules)
|
32
|
+
setup_email(nil)
|
33
|
+
content_type 'multipart/alternative'
|
34
|
+
part :content_type => 'text/html', :body => "#{rules}<p class=\"text\">Hello World</p>"
|
19
35
|
part :content_type => 'text/plain', :body => 'Hello World'
|
20
36
|
end
|
21
37
|
|
@@ -50,6 +66,47 @@ class TestMailer < ActionMailer::Base
|
|
50
66
|
end
|
51
67
|
end
|
52
68
|
|
69
|
+
shared_examples_for "inline styles" do
|
70
|
+
before(:each) do
|
71
|
+
css_rules <<-EOF
|
72
|
+
body { background: #000 }
|
73
|
+
.text { color: #0f0; font-size: 14px }
|
74
|
+
p { color: #f00; line-height: 1.5 }
|
75
|
+
EOF
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should add the correct xml namespace" do
|
79
|
+
should match(/<html xmlns="http:\/\/www\.w3\.org\/1999\/xhtml">/)
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should write the xhtml 1.0 doctype" do
|
83
|
+
should match(/<!DOCTYPE html PUBLIC "-\/\/W3C\/\/DTD XHTML 1\.0 Transitional\/\/EN" "http:\/\/www.w3.org\/TR\/xhtml1\/DTD\/xhtml1-transitional\.dtd">/mi)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should write utf-8 content type meta tag" do
|
87
|
+
should match(/<head>.*<meta http\-equiv="Content\-Type" content="text\/html; charset=utf\-8">.*<\/head>/mi)
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should wrap with html and body tag if missing" do
|
91
|
+
should match(/<html.*>.*<body.*>.*<\/body>.*<\/html>/m)
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should add style to body" do
|
95
|
+
should match(/<body style="background: #000">/)
|
96
|
+
end
|
97
|
+
|
98
|
+
it "should add both styles to paragraph" do
|
99
|
+
should match(/<p class="text" style="color: #0f0;font-size: 14px;line-height: 1.5">/)
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should not crash on :pseudo-classes" do
|
103
|
+
css_rules("a:link { color: #f00 }")
|
104
|
+
expect do
|
105
|
+
subject
|
106
|
+
end.to_not raise_error(StandardError)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
53
110
|
describe 'Inline styles' do
|
54
111
|
describe 'singlepart' do
|
55
112
|
before(:each) do
|
@@ -95,41 +152,9 @@ describe 'Inline styles' do
|
|
95
152
|
end
|
96
153
|
|
97
154
|
describe 'rendering inline styles' do
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
.text { color: #0f0; font-size: 14px }
|
102
|
-
p { color: #f00; line-height: 1.5 }
|
103
|
-
EOF
|
104
|
-
|
105
|
-
# Generate email
|
106
|
-
@email = TestMailer.deliver_test_multipart(:real)
|
107
|
-
@html = html_part(@email)
|
108
|
-
end
|
109
|
-
|
110
|
-
it "should add the correct xml namespace" do
|
111
|
-
@html.should match(/<html xmlns="http:\/\/www\.w3\.org\/1999\/xhtml">/)
|
112
|
-
end
|
113
|
-
|
114
|
-
it "should write the xhtml 1.0 doctype" do
|
115
|
-
@html.should match(/<!DOCTYPE html PUBLIC "-\/\/W3C\/\/DTD XHTML 1\.0 Transitional\/\/EN" "http:\/\/www.w3.org\/TR\/xhtml1\/DTD\/xhtml1-transitional\.dtd">/mi)
|
116
|
-
end
|
117
|
-
|
118
|
-
it "should write utf-8 content type meta tag" do
|
119
|
-
@html.should match(/<head>.*<meta http\-equiv="Content\-Type" content="text\/html; charset=utf\-8">.*<\/head>/mi)
|
120
|
-
end
|
121
|
-
|
122
|
-
it "should wrap with html and body tag if missing" do
|
123
|
-
@html.should match(/<html.*>.*<body.*>.*<\/body>.*<\/html>/m)
|
124
|
-
end
|
125
|
-
|
126
|
-
it "should add style to body" do
|
127
|
-
@html.should match(/<body style="background: #000">/)
|
128
|
-
end
|
129
|
-
|
130
|
-
it "should add both styles to paragraph" do
|
131
|
-
@html.should match(/<p class="text" style="color: #0f0;font-size: 14px;line-height: 1.5">/)
|
132
|
-
end
|
155
|
+
let(:email) { TestMailer.deliver_test_multipart(:real) }
|
156
|
+
subject { html_part(email) }
|
157
|
+
it_should_behave_like("inline styles")
|
133
158
|
end
|
134
159
|
|
135
160
|
describe 'combining styles' do
|
@@ -155,9 +180,13 @@ describe 'Inline styles' do
|
|
155
180
|
end
|
156
181
|
|
157
182
|
describe 'css file' do
|
158
|
-
it "should
|
183
|
+
it "should not change the styles nothing if no css file is set" do
|
184
|
+
css_rules <<-EOF
|
185
|
+
.text { color: #0f0; }
|
186
|
+
p { color: #f00; }
|
187
|
+
EOF
|
159
188
|
@email = TestMailer.deliver_test_multipart(nil)
|
160
|
-
html_part(@email).should
|
189
|
+
html_part(@email).should match(/<p class="text">/)
|
161
190
|
end
|
162
191
|
|
163
192
|
it "should raise MailStyle::CSSFileNotFound if css file does not exist" do
|
@@ -184,4 +213,49 @@ describe 'Inline styles' do
|
|
184
213
|
@email.parts.length.should eql(2)
|
185
214
|
end
|
186
215
|
end
|
216
|
+
|
217
|
+
describe "multipart mixed" do
|
218
|
+
let(:email) { TestMailer.deliver_test_nested_multipart_mixed(:real) }
|
219
|
+
subject { html_part(email) }
|
220
|
+
it_should_behave_like("inline styles")
|
221
|
+
end
|
222
|
+
|
223
|
+
describe "inline rules" do
|
224
|
+
let(:email) { TestMailer.deliver_test_inline_rules("<style> .text { color: #f00; line-height: 1.5 } </style>") }
|
225
|
+
subject { html_part(email) }
|
226
|
+
|
227
|
+
it "should style the elements with rules inside the document" do
|
228
|
+
should match(/<p class="text" style="color:\s+#f00;\s*line-height:\s+1.5">/)
|
229
|
+
end
|
230
|
+
|
231
|
+
it "should remove the styles from the document" do
|
232
|
+
should_not match(/<style/)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
describe "inline rules for print media" do
|
237
|
+
let(:email) { TestMailer.deliver_test_inline_rules('<style media="print"> .text { color: #f00; } </style>') }
|
238
|
+
subject { html_part(email) }
|
239
|
+
|
240
|
+
it "should not change element styles" do
|
241
|
+
should match(/<p class="text">/)
|
242
|
+
end
|
243
|
+
|
244
|
+
it "should not remove the styles from the document" do
|
245
|
+
should match(/<style media="print"/)
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
describe "inline immutable styles" do
|
250
|
+
let(:email) { TestMailer.deliver_test_inline_rules('<style data-immutable="true"> .text { color: #f00; } </style>') }
|
251
|
+
subject { html_part(email) }
|
252
|
+
|
253
|
+
it "should not change element styles" do
|
254
|
+
should match(/<p class="text">/)
|
255
|
+
end
|
256
|
+
|
257
|
+
it "should not remove the styles from the document" do
|
258
|
+
should match(/<style data-immutable="true"/)
|
259
|
+
end
|
260
|
+
end
|
187
261
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -5,9 +5,14 @@ require 'spec'
|
|
5
5
|
require 'action_mailer'
|
6
6
|
require 'mail_style'
|
7
7
|
|
8
|
+
def flatten_parts(parts)
|
9
|
+
nested = !parts.empty? ? parts.map { |p| flatten_parts(p.parts) }.flatten : []
|
10
|
+
[parts, nested].flatten
|
11
|
+
end
|
12
|
+
|
8
13
|
# Extract HTML Part
|
9
14
|
def html_part(email)
|
10
|
-
email.parts.select{|part| part.content_type == 'text/html'}.first.body
|
15
|
+
flatten_parts(email.parts).select{|part| part.content_type == 'text/html'}.first.body
|
11
16
|
end
|
12
17
|
|
13
18
|
def css_rules(css)
|