roadie 1.0.1 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Changelog.md +37 -0
- data/Gemfile.lock +2 -2
- data/README.md +151 -0
- data/lib/roadie/css_file_not_found.rb +13 -3
- data/lib/roadie/inliner.rb +26 -0
- data/lib/roadie/version.rb +1 -1
- data/roadie.gemspec +1 -1
- data/spec/fixtures/public/stylesheets/green_paragraphs.css +1 -0
- data/spec/fixtures/public/stylesheets/large_purple_paragraphs.css +1 -0
- data/spec/integration_spec.rb +3 -3
- data/spec/lib/roadie/action_mailer_extensions_spec.rb +9 -9
- data/spec/lib/roadie/css_file_not_found_spec.rb +18 -2
- data/spec/lib/roadie/inliner_spec.rb +213 -40
- data/spec/lib/roadie/style_declaration_spec.rb +5 -5
- data/spec/lib/roadie_spec.rb +4 -4
- data/spec/spec_helper.rb +2 -0
- metadata +10 -4
- data/README.textile +0 -114
data/Changelog.md
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
### dev
|
2
|
+
|
3
|
+
[full changelog](https://github.com/Mange/roadie/compare/v1.1.0...master)
|
4
|
+
|
5
|
+
* Nothing yet
|
6
|
+
|
7
|
+
### 1.1.0
|
8
|
+
|
9
|
+
[full changelog](https://github.com/Mange/roadie/compare/v1.0.1...v1.1.0)
|
10
|
+
|
11
|
+
* Enhancements:
|
12
|
+
* Support for inlining `<link>` elements (thanks to [aliix](https://github.com/aliix))
|
13
|
+
|
14
|
+
### 1.0.1
|
15
|
+
|
16
|
+
[full changelog](https://github.com/Mange/roadie/compare/v1.0.0...v1.0.1)
|
17
|
+
|
18
|
+
* Enhancements:
|
19
|
+
* Full, official support for Ruby 1.9.2 (in addition to 1.8.7)
|
20
|
+
* Dependencies:
|
21
|
+
* Explicilty depend on nokogiri >= 1.4.4
|
22
|
+
|
23
|
+
### 1.0.0
|
24
|
+
|
25
|
+
[full changelog](https://github.com/Mange/roadie/compare/legacy...v1.0.0)
|
26
|
+
|
27
|
+
Roadie fork!
|
28
|
+
|
29
|
+
* Enhancements:
|
30
|
+
* Support for Rails 3.0
|
31
|
+
* Code cleanup
|
32
|
+
* Support `!important`
|
33
|
+
* Tests
|
34
|
+
* + some other enhancements
|
35
|
+
* Deprecations:
|
36
|
+
* Removed support for Rails 2.x
|
37
|
+
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
roadie (1.0.
|
4
|
+
roadie (1.0.1)
|
5
5
|
actionmailer (~> 3.0.0)
|
6
6
|
css_parser
|
7
7
|
nokogiri (>= 1.4.4)
|
@@ -40,7 +40,7 @@ GEM
|
|
40
40
|
mime-types (~> 1.16)
|
41
41
|
treetop (~> 1.4.8)
|
42
42
|
mime-types (1.16)
|
43
|
-
nokogiri (1.4.
|
43
|
+
nokogiri (1.4.5)
|
44
44
|
polyglot (0.3.1)
|
45
45
|
rack (1.2.1)
|
46
46
|
rack-mount (0.6.13)
|
data/README.md
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
Roadie
|
2
|
+
======
|
3
|
+
|
4
|
+
> Making HTML emails comfortable for the Rails rockstars
|
5
|
+
|
6
|
+
Roadie tries to make sending HTML emails a little less painful in Rails 3 by inlining stylesheets and rewrite relative URLs for you.
|
7
|
+
|
8
|
+
If you want to have this in Rails 2, please see [MailStyle](https://www.github.com/purify/mail_style).
|
9
|
+
|
10
|
+
How does it work?
|
11
|
+
-----------------
|
12
|
+
|
13
|
+
Email clients have bad support for stylesheets, and some of them blocks stylesheets from downloading. The easiest way to handle this is to work with all styles inline, but that is error prone and hard to work with as you cannot use classes and/or reuse styling.
|
14
|
+
|
15
|
+
This gem helps making this easier by automatically inlining stylesheet rules into the document before sending it. You just give it a list of stylesheets and it will go though all of the selectors assigning the styles to the maching elements. Careful attention has been put into rules being applied in the correct order, so it should behave just like in the browser¹.
|
16
|
+
|
17
|
+
Roadie also rewrites all relative URLs in the email to a absolute counterpart, making images you insert and those referenced in your stylesheets work. No more headaches about how to write the stylesheets while still having them work with emails from your acceptance environments.
|
18
|
+
|
19
|
+
¹: Of course, rules like `:hover` will not work by definition. Only static styles can be added.
|
20
|
+
|
21
|
+
Features
|
22
|
+
--------
|
23
|
+
|
24
|
+
* Writes CSS styles inline
|
25
|
+
* Respects `!important` styles
|
26
|
+
* Does not overwrite styles already present in the `style` attribute of tags
|
27
|
+
* Supports the same CSS selectors as [Nokogiri](http://nokogiri.org/) (use CSS3 selectors in your emails!)
|
28
|
+
* Makes image urls absolute
|
29
|
+
* Hostname and port configurable on a per-environment basis
|
30
|
+
* Makes link `href`s absolute
|
31
|
+
* Automatically adds proper html skeleton when missing (you don't have to create a layout for emails)²
|
32
|
+
|
33
|
+
²: This might be removed in a future version, though. You really ought to create a good layout and not let Roadie guess how you want to have it structured
|
34
|
+
|
35
|
+
### What about Sass / Less? ###
|
36
|
+
|
37
|
+
Sass is supported "by accident" as long as the stylesheets are generated and stored in the stylesheets directory. This is the default behavior from Sass. You are recommended to add a deploy task that generates the stylesheets to make sure that they are present at all times.
|
38
|
+
|
39
|
+
Install
|
40
|
+
-------
|
41
|
+
|
42
|
+
Roadie is officially supported in both Ruby 1.8.7 and 1.9.2.
|
43
|
+
|
44
|
+
Add the gem to Rails' Gemfile
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
gem 'roadie'
|
48
|
+
```
|
49
|
+
|
50
|
+
Usage
|
51
|
+
-----
|
52
|
+
|
53
|
+
Simply specify the `:css` option to mailer:
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
class Notifier < ActionMailer::Base
|
57
|
+
default :css => :email, :from => 'support@mycompany.com'
|
58
|
+
|
59
|
+
def registration_mail
|
60
|
+
mail(:subject => 'Welcome Aboard', :to => 'someone@example.com')
|
61
|
+
end
|
62
|
+
|
63
|
+
def newsletter
|
64
|
+
mail(:subject => 'Newsletter', :to => 'someone@example.com', :css => [:email, :newsletter])
|
65
|
+
end
|
66
|
+
end
|
67
|
+
```
|
68
|
+
|
69
|
+
This will look for a css file called `email.css` in your `public/stylesheets` folder. The `css` method can take either a string, a symbol or an array of both. You should pass the CSS filename without the ".css" extension.
|
70
|
+
|
71
|
+
### Image URL rewriting ###
|
72
|
+
|
73
|
+
If you have `default_url_options[:host]` set in your mailer, then Roadie will do it's best to make the URLs of images and in stylesheets absolute.
|
74
|
+
|
75
|
+
In `application.rb`:
|
76
|
+
|
77
|
+
```ruby
|
78
|
+
class Application
|
79
|
+
config.action_mailer.default_url_options = {:host => 'example.com'}
|
80
|
+
end
|
81
|
+
```
|
82
|
+
|
83
|
+
If you want to to be different depending on your environment, just set it in your environment's configuration instead.
|
84
|
+
|
85
|
+
### Ignoring stylesheets ###
|
86
|
+
|
87
|
+
By default, `style` and `link` elements in the email document's `head` are processed along with the stylesheets and removed from the `head`.
|
88
|
+
|
89
|
+
You can set a special `data-immutable="true"` attribute on `style` and `link` tags you do not want to be processed and removed from the document's `head`. This is the place to put things like `:hover` selectors that you want to have for email clients allowing them.
|
90
|
+
|
91
|
+
Style and link elements with `media="print"` are always ignored.
|
92
|
+
|
93
|
+
### Inlining link tags ###
|
94
|
+
|
95
|
+
Any `link` element that is part of your email will be linked in. You can exclude them by setting `data-immutable` as you would on normal `style` elements. Linked stylesheets for print media is also ignored as you would expect.
|
96
|
+
|
97
|
+
If the `link` tag uses an absolute URL to the stylesheet, it will not be inlined. Use a relative path instead:
|
98
|
+
|
99
|
+
```html
|
100
|
+
<head>
|
101
|
+
<link rel="stylesheet" type="text/css" href="/stylesheets/emails/rock.css"> <!-- Will be inlined -->
|
102
|
+
<link rel="stylesheet" type="text/css" href="http://www.metal.org/metal.css"> <!-- Will NOT be inlined -->
|
103
|
+
<link rel="stylesheet" type="text/css" href="/stylesheets/jazz.css" media="print"> <!-- Will NOT be inlined -->
|
104
|
+
<link rel="stylesheet" type="text/css" href="/ambient.css" data-immutable> <!-- Will NOT be inlined -->
|
105
|
+
</head>
|
106
|
+
```
|
107
|
+
|
108
|
+
Bugs / TODO
|
109
|
+
-----------
|
110
|
+
|
111
|
+
* Improve overall performance
|
112
|
+
* Clean up stylesheet assignment code
|
113
|
+
|
114
|
+
Documentation
|
115
|
+
-------------
|
116
|
+
|
117
|
+
* [Online documentation for 1.0.1](http://rubydoc.info/gems/roadie/1.0.1/frames)
|
118
|
+
* [Online documentation for 1.0.0](http://rubydoc.info/gems/roadie/1.0.0/frames)
|
119
|
+
* [Online documentation for master](http://rubydoc.info/github/Mange/roadie/master/frames)
|
120
|
+
* [Changelog](https://github.com/Mange/roadie/blob/master/Changelog.md)
|
121
|
+
|
122
|
+
History and contributors
|
123
|
+
------------------------
|
124
|
+
|
125
|
+
This gem was originally developed for Rails 2 use on [Purify](http://purifyapp.com) under the name [MailStyle](https://www.github.com/purify/mail_style). However, the author stopped maintaining it and a fork took place to make it Rails 3 compatible.
|
126
|
+
|
127
|
+
The following people have contributed to the orignal gem:
|
128
|
+
|
129
|
+
* [Jim Neath](http://jimneath.org) (Original author)
|
130
|
+
* [Lars Klevans](http://tastybyte.blogspot.com/)
|
131
|
+
* [Jonas Grimfelt](http://github.com/grimen)
|
132
|
+
* [Ben Johnson](http://www.binarylogic.com)
|
133
|
+
* [Istvan Hoka](http://istvanhoka.com/)
|
134
|
+
* [Voraz](http://blog.voraz.com.br)
|
135
|
+
|
136
|
+
License
|
137
|
+
-------
|
138
|
+
|
139
|
+
(The MIT License)
|
140
|
+
|
141
|
+
Copyright (c) 2009-2011
|
142
|
+
|
143
|
+
* [Jim Neath](http://jimneath.org)
|
144
|
+
* Magnus Bergmark <magnus.bergmark@gmail.com>
|
145
|
+
|
146
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ‘Software’), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
147
|
+
|
148
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
149
|
+
|
150
|
+
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.
|
151
|
+
|
@@ -2,11 +2,21 @@ module Roadie
|
|
2
2
|
# Raised when a stylesheet specified for inlining is not present.
|
3
3
|
# You can access the target filename via #filename.
|
4
4
|
class CSSFileNotFound < StandardError
|
5
|
-
attr_reader :filename
|
5
|
+
attr_reader :filename, :guess
|
6
6
|
|
7
|
-
def initialize(filename)
|
7
|
+
def initialize(filename, guess = nil)
|
8
8
|
@filename = filename
|
9
|
-
|
9
|
+
@guess = guess
|
10
|
+
super(build_message)
|
10
11
|
end
|
12
|
+
|
13
|
+
private
|
14
|
+
def build_message
|
15
|
+
if guess
|
16
|
+
"Could not find #{filename} (guessed from #{guess.inspect})"
|
17
|
+
else
|
18
|
+
"Could not find #{filename}"
|
19
|
+
end
|
20
|
+
end
|
11
21
|
end
|
12
22
|
end
|
data/lib/roadie/inliner.rb
CHANGED
@@ -39,6 +39,7 @@ module Roadie
|
|
39
39
|
adjust_html do |document|
|
40
40
|
@document = document
|
41
41
|
add_missing_structure
|
42
|
+
extract_link_elements
|
42
43
|
extract_inline_style_elements
|
43
44
|
inline_css_rules
|
44
45
|
make_image_urls_absolute
|
@@ -88,6 +89,18 @@ module Roadie
|
|
88
89
|
end
|
89
90
|
end
|
90
91
|
|
92
|
+
def extract_link_elements
|
93
|
+
all_link_elements_to_be_inlined_with_url.each do |link, url|
|
94
|
+
# Joining on an "absolute" path ignores everything before the absoluted path
|
95
|
+
# so we have to remove the starting slash
|
96
|
+
url_path = url.path.sub(%r{^/}, '')
|
97
|
+
file_path = Rails.root.join('public', url_path)
|
98
|
+
raise CSSFileNotFound.new(file_path, link['href']) unless file_path.file?
|
99
|
+
@inline_css << file_path.read
|
100
|
+
link.remove
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
91
104
|
def extract_inline_style_elements
|
92
105
|
document.css("style").each do |style|
|
93
106
|
next if style['media'] == 'print' or style['data-immutable']
|
@@ -177,5 +190,18 @@ module Roadie
|
|
177
190
|
:path => base_path
|
178
191
|
})
|
179
192
|
end
|
193
|
+
|
194
|
+
def all_link_elements_with_url
|
195
|
+
document.css("link[rel=stylesheet]").map { |link| [link, URI.parse(link['href'])] }
|
196
|
+
end
|
197
|
+
|
198
|
+
def all_link_elements_to_be_inlined_with_url
|
199
|
+
all_link_elements_with_url.reject do |link, url|
|
200
|
+
absolute_path_url = (url.host or url.path.nil?)
|
201
|
+
blacklisted_element = (link['media'] == 'print' or link['data-immutable'])
|
202
|
+
|
203
|
+
absolute_path_url or blacklisted_element
|
204
|
+
end
|
205
|
+
end
|
180
206
|
end
|
181
207
|
end
|
data/lib/roadie/version.rb
CHANGED
data/roadie.gemspec
CHANGED
@@ -0,0 +1 @@
|
|
1
|
+
p { color: green; }
|
@@ -0,0 +1 @@
|
|
1
|
+
p { color: purple; font-size: 18px; }
|
data/spec/integration_spec.rb
CHANGED
@@ -27,7 +27,7 @@ describe "roadie integration" do
|
|
27
27
|
IntegrationMailer.delivery_method = :test
|
28
28
|
end
|
29
29
|
|
30
|
-
it "
|
30
|
+
it "inlines styles for an email" do
|
31
31
|
email = IntegrationMailer.notification('doe@example.com', 'your quota limit has been reached')
|
32
32
|
|
33
33
|
email.to.should == ['doe@example.com']
|
@@ -50,12 +50,12 @@ describe "roadie integration" do
|
|
50
50
|
email.deliver
|
51
51
|
end
|
52
52
|
|
53
|
-
it "
|
53
|
+
it "does not add headers for the roadie options" do
|
54
54
|
email = IntegrationMailer.notification('doe@example.com', 'no berries left in chest')
|
55
55
|
email.header.fields.map(&:name).should_not include('css')
|
56
56
|
end
|
57
57
|
|
58
|
-
it "
|
58
|
+
it "keeps custom headers in place" do
|
59
59
|
email = IntegrationMailer.marketing('everyone@inter.net')
|
60
60
|
email.header['X-Spam'].should be_present
|
61
61
|
end
|
@@ -31,25 +31,25 @@ describe Roadie::ActionMailerExtensions, "inlining styles" do
|
|
31
31
|
end
|
32
32
|
|
33
33
|
describe "for singlepart text/plain" do
|
34
|
-
it "
|
34
|
+
it "does not touch the email body" do
|
35
35
|
Roadie.should_not_receive(:inline_css)
|
36
36
|
InliningMailer.singlepart_plain
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
40
40
|
describe "for singlepart text/html" do
|
41
|
-
it "
|
41
|
+
it "inlines css to the email body" do
|
42
42
|
Roadie.should_receive(:inline_css).with(anything, 'Hello HTML', anything).and_return('html')
|
43
43
|
InliningMailer.singlepart_html.body.decoded.should == 'html'
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
47
47
|
describe "for multipart" do
|
48
|
-
it "
|
48
|
+
it "keeps both parts" do
|
49
49
|
InliningMailer.multipart.should have(2).parts
|
50
50
|
end
|
51
51
|
|
52
|
-
it "
|
52
|
+
it "inlines css to the email's html part" do
|
53
53
|
Roadie.should_receive(:inline_css).with(anything, 'Hello HTML', anything).and_return('html')
|
54
54
|
email = InliningMailer.multipart
|
55
55
|
email.html_part.body.decoded.should == 'html'
|
@@ -79,27 +79,27 @@ describe Roadie::ActionMailerExtensions, "loading css files" do
|
|
79
79
|
Roadie.stub!(:inline_css => 'html')
|
80
80
|
end
|
81
81
|
|
82
|
-
it "
|
82
|
+
it "loads css from Rails' stylesheet root" do
|
83
83
|
Roadie.should_receive(:load_css).with(Rails.root.join('public', 'stylesheets'), anything).and_return('')
|
84
84
|
CssLoadingMailer.use_default
|
85
85
|
end
|
86
86
|
|
87
|
-
it "
|
87
|
+
it "loads the css specified in the default mailer settings" do
|
88
88
|
Roadie.should_receive(:load_css).with(anything, ['default_value']).and_return('')
|
89
89
|
CssLoadingMailer.use_default
|
90
90
|
end
|
91
91
|
|
92
|
-
it "
|
92
|
+
it "loads the css specified in the specific mailer action instead of the default choice" do
|
93
93
|
Roadie.should_receive(:load_css).with(anything, ['specific']).and_return('')
|
94
94
|
CssLoadingMailer.override(:specific)
|
95
95
|
end
|
96
96
|
|
97
|
-
it "
|
97
|
+
it "loads no css when specifying false in the mailer action" do
|
98
98
|
Roadie.should_not_receive(:load_css)
|
99
99
|
CssLoadingMailer.override(false)
|
100
100
|
end
|
101
101
|
|
102
|
-
it "
|
102
|
+
it "loads multiple css files when given an array" do
|
103
103
|
Roadie.should_receive(:load_css).with(anything, ['specific', 'other']).and_return('')
|
104
104
|
CssLoadingMailer.override([:specific, :other])
|
105
105
|
end
|
@@ -6,8 +6,24 @@ module Roadie
|
|
6
6
|
CSSFileNotFound.new('file.css').filename.should == 'file.css'
|
7
7
|
end
|
8
8
|
|
9
|
-
it "
|
10
|
-
CSSFileNotFound.new('
|
9
|
+
it "can be initialized with the guess the filename was based on" do
|
10
|
+
CSSFileNotFound.new('file.css', :file).guess.should == :file
|
11
|
+
end
|
12
|
+
|
13
|
+
it "has a nil guess when no guess was specified" do
|
14
|
+
CSSFileNotFound.new('').guess.should be_nil
|
15
|
+
end
|
16
|
+
|
17
|
+
context "without a guess" do
|
18
|
+
it "has a message with the wanted filename" do
|
19
|
+
CSSFileNotFound.new('style.css').message.should == 'Could not find style.css'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context "with a guess" do
|
24
|
+
it "has a message with the wanted filename and the guess" do
|
25
|
+
CSSFileNotFound.new('style.css', :style).message.should == 'Could not find style.css (guessed from :style)'
|
26
|
+
end
|
11
27
|
end
|
12
28
|
end
|
13
29
|
end
|
@@ -15,29 +15,29 @@ describe Roadie::Inliner do
|
|
15
15
|
use_css ''
|
16
16
|
end
|
17
17
|
|
18
|
-
it "
|
18
|
+
it "inlines simple attributes" do
|
19
19
|
use_css 'p { color: green }'
|
20
20
|
rendering('<p></p>').should have_styling('color' => 'green')
|
21
21
|
end
|
22
22
|
|
23
|
-
it "
|
23
|
+
it "keeps the order of the styles that are inlined" do
|
24
24
|
use_css 'h1 { padding: 2px; margin: 5px; }'
|
25
25
|
rendering('<h1></h1>').should have_styling([['padding', '2px'], ['margin', '5px']])
|
26
26
|
end
|
27
27
|
|
28
|
-
it "
|
28
|
+
it "combines multiple selectors into one" do
|
29
29
|
use_css 'p { color: green; }
|
30
30
|
.tip { float: right; }'
|
31
31
|
rendering('<p class="tip"></p>').should have_styling('color' => 'green', 'float' => 'right')
|
32
32
|
end
|
33
33
|
|
34
|
-
it "
|
34
|
+
it "uses the attributes with the highest specificity when conflicts arises" do
|
35
35
|
use_css "p { color: red; }
|
36
36
|
.safe { color: green; }"
|
37
37
|
rendering('<p class="safe"></p>').should have_styling('color' => 'green')
|
38
38
|
end
|
39
39
|
|
40
|
-
it "
|
40
|
+
it "sorts styles by specificity order" do
|
41
41
|
use_css 'p { margin: 2px; }
|
42
42
|
#big { margin: 10px; }
|
43
43
|
.down { margin-bottom: 5px; }'
|
@@ -51,7 +51,7 @@ describe Roadie::Inliner do
|
|
51
51
|
])
|
52
52
|
end
|
53
53
|
|
54
|
-
it "
|
54
|
+
it "supports multiple selectors for the same rules" do
|
55
55
|
use_css 'p, a { color: green; }'
|
56
56
|
rendering('<p></p><a></a>').tap do |document|
|
57
57
|
document.should have_styling('color' => 'green').at_selector('p')
|
@@ -59,29 +59,29 @@ describe Roadie::Inliner do
|
|
59
59
|
end
|
60
60
|
end
|
61
61
|
|
62
|
-
it "
|
62
|
+
it "respects !important properties" do
|
63
63
|
use_css "a { text-decoration: underline !important; }
|
64
64
|
a.hard-to-spot { text-decoration: none; }"
|
65
65
|
rendering('<a class="hard-to-spot"></a>').should have_styling('text-decoration' => 'underline')
|
66
66
|
end
|
67
67
|
|
68
|
-
it "
|
68
|
+
it "combines with already present inline styles" do
|
69
69
|
use_css "p { color: green }"
|
70
70
|
rendering('<p style="font-size: 1.1em"></p>').should have_styling([['color', 'green'], ['font-size', '1.1em']])
|
71
71
|
end
|
72
72
|
|
73
|
-
it "
|
73
|
+
it "does not touch already present inline styles" do
|
74
74
|
use_css "p { color: red }"
|
75
75
|
rendering('<p style="color: green"></p>').should have_styling([['color', 'red'], ['color', 'green']])
|
76
76
|
end
|
77
77
|
|
78
|
-
it "
|
78
|
+
it "ignores selectors with :psuedo-classes" do
|
79
79
|
use_css 'p:hover { color: red }'
|
80
80
|
rendering('<p></p>').should_not have_styling('color' => 'red')
|
81
81
|
end
|
82
82
|
|
83
|
-
describe "inline <style>
|
84
|
-
it "
|
83
|
+
describe "inline <style> element" do
|
84
|
+
it "is used for inlined styles" do
|
85
85
|
rendering(<<-HTML).should have_styling([['color', 'green'], ['font-size', '1.1em']])
|
86
86
|
<html>
|
87
87
|
<head>
|
@@ -95,7 +95,7 @@ describe Roadie::Inliner do
|
|
95
95
|
HTML
|
96
96
|
end
|
97
97
|
|
98
|
-
it "
|
98
|
+
it "is removed" do
|
99
99
|
rendering(<<-HTML).should_not have_selector('style')
|
100
100
|
<html>
|
101
101
|
<head>
|
@@ -108,16 +108,16 @@ describe Roadie::Inliner do
|
|
108
108
|
HTML
|
109
109
|
end
|
110
110
|
|
111
|
-
it "
|
111
|
+
it "is not touched when data-immutable is set" do
|
112
112
|
document = rendering <<-HTML
|
113
|
-
<style type="text/css" data-immutable
|
113
|
+
<style type="text/css" data-immutable>p { color: red; }</style>
|
114
114
|
<p></p>
|
115
115
|
HTML
|
116
|
-
document.should have_selector('style[data-immutable
|
116
|
+
document.should have_selector('style[data-immutable]')
|
117
117
|
document.should_not have_styling('color' => 'red')
|
118
118
|
end
|
119
119
|
|
120
|
-
it "
|
120
|
+
it "is not touched when for print media" do
|
121
121
|
document = rendering <<-HTML
|
122
122
|
<style type="text/css" media="print">p { color: red; }</style>
|
123
123
|
<p></p>
|
@@ -126,7 +126,9 @@ describe Roadie::Inliner do
|
|
126
126
|
document.should_not have_styling('color' => 'red').at_selector('p')
|
127
127
|
end
|
128
128
|
|
129
|
-
it "
|
129
|
+
it "is still inlined when no external css rules are defined" do
|
130
|
+
# This is just testing that the main code paths are still active even
|
131
|
+
# when css is set to nil
|
130
132
|
use_css nil
|
131
133
|
rendering(<<-HTML).should have_styling('color' => 'green').at_selector('p')
|
132
134
|
<style type="text/css">p { color: green; }</style>
|
@@ -136,23 +138,194 @@ describe Roadie::Inliner do
|
|
136
138
|
end
|
137
139
|
end
|
138
140
|
|
141
|
+
describe "linked stylesheets" do
|
142
|
+
before(:each) do
|
143
|
+
Rails.stub(:root => FixturesPath)
|
144
|
+
end
|
145
|
+
|
146
|
+
it "inlines styles from the linked stylesheet" do
|
147
|
+
rendering(<<-HTML).should have_styling('color' => 'green').at_selector('p')
|
148
|
+
<html>
|
149
|
+
<head>
|
150
|
+
<link rel="stylesheet" href="/stylesheets/green_paragraphs.css">
|
151
|
+
</head>
|
152
|
+
<body>
|
153
|
+
<p></p>
|
154
|
+
</body>
|
155
|
+
</html>
|
156
|
+
HTML
|
157
|
+
end
|
158
|
+
|
159
|
+
it "inlines styles from more than one linked stylesheet" do
|
160
|
+
html = <<-HTML
|
161
|
+
<html>
|
162
|
+
<head>
|
163
|
+
<link rel="stylesheet" href="/stylesheets/green_paragraphs.css">
|
164
|
+
<link rel="stylesheet" href="/stylesheets/large_purple_paragraphs.css">
|
165
|
+
</head>
|
166
|
+
<body>
|
167
|
+
<p></p>
|
168
|
+
</body>
|
169
|
+
</html>
|
170
|
+
HTML
|
171
|
+
|
172
|
+
rendering(html).should have_styling([
|
173
|
+
['color', 'purple'],
|
174
|
+
['font-size', '18px'],
|
175
|
+
]).at_selector('p')
|
176
|
+
end
|
177
|
+
|
178
|
+
it "removes the stylesheet links from the DOM" do
|
179
|
+
rendering(<<-HTML).should_not have_selector('link')
|
180
|
+
<html>
|
181
|
+
<head>
|
182
|
+
<link rel="stylesheet" href="/stylesheets/green_paragraphs.css">
|
183
|
+
<link rel="stylesheet" href="/stylesheets/large_purple_paragraphs.css">
|
184
|
+
</head>
|
185
|
+
<body>
|
186
|
+
</body>
|
187
|
+
</html>
|
188
|
+
HTML
|
189
|
+
end
|
190
|
+
|
191
|
+
context "when stylesheet is for print media" do
|
192
|
+
it "does not inline the stylesheet" do
|
193
|
+
rendering(<<-HTML).should_not have_styling('color' => 'green').at_selector('p')
|
194
|
+
<html>
|
195
|
+
<head>
|
196
|
+
<link rel="stylesheet" href="/stylesheets/green_paragraphs.css" media="print">
|
197
|
+
</head>
|
198
|
+
<body>
|
199
|
+
<p></p>
|
200
|
+
</body>
|
201
|
+
</html>
|
202
|
+
HTML
|
203
|
+
end
|
204
|
+
|
205
|
+
it "does not remove the links" do
|
206
|
+
rendering(<<-HTML).should have_selector('link')
|
207
|
+
<html>
|
208
|
+
<head>
|
209
|
+
<link rel="stylesheet" href="/stylesheets/green_paragraphs.css" media="print">
|
210
|
+
</head>
|
211
|
+
<body>
|
212
|
+
</body>
|
213
|
+
</html>
|
214
|
+
HTML
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
context "when stylesheet is marked as immutable" do
|
219
|
+
it "does not inline the stylesheet" do
|
220
|
+
rendering(<<-HTML).should_not have_styling('color' => 'green').at_selector('p')
|
221
|
+
<html>
|
222
|
+
<head>
|
223
|
+
<link rel="stylesheet" href="/stylesheets/green_paragraphs.css" data-immutable="true">
|
224
|
+
</head>
|
225
|
+
<body>
|
226
|
+
<p></p>
|
227
|
+
</body>
|
228
|
+
</html>
|
229
|
+
HTML
|
230
|
+
end
|
231
|
+
|
232
|
+
it "does not remove link" do
|
233
|
+
rendering(<<-HTML).should have_selector('link')
|
234
|
+
<html>
|
235
|
+
<head>
|
236
|
+
<link rel="stylesheet" href="/stylesheets/green_paragraphs.css" data-immutable="true">
|
237
|
+
</head>
|
238
|
+
<body>
|
239
|
+
</body>
|
240
|
+
</html>
|
241
|
+
HTML
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
context "when stylesheet link uses an absolute URL" do
|
246
|
+
it "does not inline the stylesheet" do
|
247
|
+
rendering(<<-HTML).should_not have_styling('color' => 'green').at_selector('p')
|
248
|
+
<html>
|
249
|
+
<head>
|
250
|
+
<link rel="stylesheet" href="http://www.example.com/green_paragraphs.css">
|
251
|
+
</head>
|
252
|
+
<body>
|
253
|
+
<p></p>
|
254
|
+
</body>
|
255
|
+
</html>
|
256
|
+
HTML
|
257
|
+
end
|
258
|
+
|
259
|
+
it "does not remove link" do
|
260
|
+
rendering(<<-HTML).should have_selector('link')
|
261
|
+
<html>
|
262
|
+
<head>
|
263
|
+
<link rel="stylesheet" href="http://www.example.com/green_paragraphs.css">
|
264
|
+
</head>
|
265
|
+
<body>
|
266
|
+
</body>
|
267
|
+
</html>
|
268
|
+
HTML
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
context "stylesheet cannot be found on disk" do
|
273
|
+
it "raises an error" do
|
274
|
+
html = <<-HTML
|
275
|
+
<html>
|
276
|
+
<head>
|
277
|
+
<link rel="stylesheet" href="/stylesheets/not_found.css">
|
278
|
+
</head>
|
279
|
+
<body>
|
280
|
+
</body>
|
281
|
+
</html>
|
282
|
+
HTML
|
283
|
+
|
284
|
+
expect { rendering(html) }.to raise_error do |error|
|
285
|
+
error.should be_a(Roadie::CSSFileNotFound)
|
286
|
+
error.filename.should == Rails.root.join('public', 'stylesheets', 'not_found.css')
|
287
|
+
error.guess.should == '/stylesheets/not_found.css'
|
288
|
+
end
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
context "link element is not for a stylesheet" do
|
293
|
+
it "is ignored" do
|
294
|
+
html = <<-HTML
|
295
|
+
<html>
|
296
|
+
<head>
|
297
|
+
<link rel="not_stylesheet" href="/stylesheets/green_paragraphs.css">
|
298
|
+
</head>
|
299
|
+
<body>
|
300
|
+
<p></p>
|
301
|
+
</body>
|
302
|
+
</html>
|
303
|
+
HTML
|
304
|
+
rendering(html).tap do |document|
|
305
|
+
document.should_not have_styling('color' => 'green').at_selector('p')
|
306
|
+
document.should have_selector('link')
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
139
312
|
describe "making urls absolute" do
|
140
|
-
it "
|
313
|
+
it "works on image sources" do
|
141
314
|
rendering('<img src="/images/foo.jpg" />').should have_attribute('src' => 'http://example.com/images/foo.jpg')
|
142
315
|
rendering('<img src="../images/foo.jpg" />').should have_attribute('src' => 'http://example.com/images/foo.jpg')
|
143
316
|
rendering('<img src="foo.jpg" />').should have_attribute('src' => 'http://example.com/foo.jpg')
|
144
317
|
end
|
145
318
|
|
146
|
-
it "
|
319
|
+
it "does not touch image sources that are already absolute" do
|
147
320
|
rendering('<img src="http://other.example.org/images/foo.jpg" />').should have_attribute('src' => 'http://other.example.org/images/foo.jpg')
|
148
321
|
end
|
149
322
|
|
150
|
-
it "
|
323
|
+
it "works on inlined style attributes" do
|
151
324
|
rendering('<p style="background: url(/paper.png)"></p>').should have_styling('background' => 'url(http://example.com/paper.png)')
|
152
325
|
rendering('<p style="background: url("/paper.png")"></p>').should have_styling('background' => 'url("http://example.com/paper.png")')
|
153
326
|
end
|
154
327
|
|
155
|
-
it "
|
328
|
+
it "works on external style declarations" do
|
156
329
|
use_css "p { background-image: url(/paper.png); }
|
157
330
|
table { background-image: url('/paper.png'); }
|
158
331
|
div { background-image: url(\"/paper.png\"); }"
|
@@ -161,14 +334,14 @@ describe Roadie::Inliner do
|
|
161
334
|
rendering('<div></div>').should have_styling('background-image' => 'url("http://example.com/paper.png")')
|
162
335
|
end
|
163
336
|
|
164
|
-
it "
|
337
|
+
it "does not touch style urls that are already absolute" do
|
165
338
|
external_url = 'url(http://other.example.org/paper.png)'
|
166
339
|
use_css "p { background-image: #{external_url}; }"
|
167
340
|
rendering('<p></p>').should have_styling('background-image' => external_url)
|
168
341
|
rendering(%(<div style="background-image: #{external_url}"></div>)).should have_styling('background-image' => external_url)
|
169
342
|
end
|
170
343
|
|
171
|
-
it "
|
344
|
+
it "does not touch the urls when no url options are defined" do
|
172
345
|
use_css "img { background: url(/a.jpg); }"
|
173
346
|
rendering('<img src="/b.jpg" />', :url_options => nil).tap do |document|
|
174
347
|
document.should have_attribute('src' => '/b.jpg').at_selector('img')
|
@@ -176,7 +349,7 @@ describe Roadie::Inliner do
|
|
176
349
|
end
|
177
350
|
end
|
178
351
|
|
179
|
-
it "
|
352
|
+
it "supports port and protocol settings" do
|
180
353
|
use_css "img { background: url(/a.jpg); }"
|
181
354
|
rendering('<img src="/b.jpg" />', :url_options => {:host => 'example.com', :protocol => 'https', :port => '8080'}).tap do |document|
|
182
355
|
document.should have_attribute('src' => 'https://example.com:8080/b.jpg').at_selector('img')
|
@@ -184,38 +357,38 @@ describe Roadie::Inliner do
|
|
184
357
|
end
|
185
358
|
end
|
186
359
|
|
187
|
-
it "
|
360
|
+
it "does not touch data: URIs" do
|
188
361
|
use_css "div { background: url(data:abcdef); }"
|
189
362
|
rendering('<div></div>').should have_styling('background' => 'url(data:abcdef)')
|
190
363
|
end
|
191
364
|
end
|
192
365
|
|
193
366
|
describe "inserting tags" do
|
194
|
-
it "
|
367
|
+
it "inserts a doctype if not present" do
|
195
368
|
rendering('<html><body></body></html>').to_xml.should include('<!DOCTYPE ')
|
196
369
|
rendering('<!DOCTYPE html><html><body></body></html>').to_xml.should_not match(/(DOCTYPE.*?){2}/)
|
197
370
|
end
|
198
371
|
|
199
|
-
it "
|
372
|
+
it "sets xmlns of <html> to that of XHTML" do
|
200
373
|
rendering('<html><body></body></html>').should have_selector('html[xmlns="http://www.w3.org/1999/xhtml"]')
|
201
374
|
end
|
202
375
|
|
203
|
-
it "
|
376
|
+
it "inserts basic html structure if not present" do
|
204
377
|
rendering('<h1>Hey!</h1>').should have_selector('html > head + body > h1')
|
205
378
|
end
|
206
379
|
|
207
|
-
it "
|
380
|
+
it "inserts <head> if not present" do
|
208
381
|
rendering('<html><body></body></html>').should have_selector('html > head + body')
|
209
382
|
end
|
210
383
|
|
211
|
-
it "
|
384
|
+
it "inserts meta tag describing content-type" do
|
212
385
|
rendering('<html><head></head><body></body></html>').tap do |document|
|
213
386
|
document.should have_selector('head meta[http-equiv="Content-Type"]')
|
214
387
|
document.css('head meta[http-equiv="Content-Type"]').first['content'].should == 'text/html; charset=UTF-8'
|
215
388
|
end
|
216
389
|
end
|
217
390
|
|
218
|
-
it "
|
391
|
+
it "does not insert duplicate meta tags describing content-type" do
|
219
392
|
rendering(<<-HTML).to_html.scan('meta').should have(1).item
|
220
393
|
<html>
|
221
394
|
<head>
|
@@ -227,15 +400,15 @@ describe Roadie::Inliner do
|
|
227
400
|
end
|
228
401
|
|
229
402
|
describe "css url regex" do
|
230
|
-
it "
|
403
|
+
it "parses css urls" do
|
231
404
|
{
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
405
|
+
%{url(/foo.jpg)} => '/foo.jpg',
|
406
|
+
%{url("/foo.jpg")} => '/foo.jpg',
|
407
|
+
%{url('/foo.jpg')} => '/foo.jpg',
|
408
|
+
%{url(http://localhost/foo.jpg)} => 'http://localhost/foo.jpg',
|
409
|
+
%{url("http://localhost/foo.jpg")} => 'http://localhost/foo.jpg',
|
410
|
+
%{url('http://localhost/foo.jpg')} => 'http://localhost/foo.jpg',
|
411
|
+
%{url(/andromeda_(galaxy).jpg)} => '/andromeda_(galaxy).jpg',
|
239
412
|
}.each do |raw, expected|
|
240
413
|
raw =~ Roadie::Inliner::CSS_URL_REGEXP
|
241
414
|
$2.should == expected
|
@@ -2,7 +2,7 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
module Roadie
|
4
4
|
describe StyleDeclaration do
|
5
|
-
it "
|
5
|
+
it "is initialized with a property, value, if it is marked as important, and the specificity" do
|
6
6
|
StyleDeclaration.new('color', 'green', true, 45).tap do |declaration|
|
7
7
|
declaration.property.should == 'color'
|
8
8
|
declaration.value.should == 'green'
|
@@ -12,7 +12,7 @@ module Roadie
|
|
12
12
|
end
|
13
13
|
|
14
14
|
describe "string representation" do
|
15
|
-
it "
|
15
|
+
it "is the property and the value joined with a colon" do
|
16
16
|
StyleDeclaration.new('color', 'green', false, 1).to_s.should == 'color:green'
|
17
17
|
StyleDeclaration.new('font-size', '1.1em', false, 1).to_s.should == 'font-size:1.1em'
|
18
18
|
end
|
@@ -23,18 +23,18 @@ module Roadie
|
|
23
23
|
StyleDeclaration.new('color', 'green', important, specificity)
|
24
24
|
end
|
25
25
|
|
26
|
-
it "
|
26
|
+
it "compares on specificity" do
|
27
27
|
declaration(5).should be == declaration(5)
|
28
28
|
declaration(4).should be < declaration(5)
|
29
29
|
declaration(6).should be > declaration(5)
|
30
30
|
end
|
31
31
|
|
32
32
|
context "with an important declaration" do
|
33
|
-
it "
|
33
|
+
it "is less than the important declaration regardless of the specificity" do
|
34
34
|
declaration(99, false).should be < declaration(1, true)
|
35
35
|
end
|
36
36
|
|
37
|
-
it "
|
37
|
+
it "compares like normal when both declarations are important" do
|
38
38
|
declaration(5, true).should be == declaration(5, true)
|
39
39
|
declaration(4, true).should be < declaration(5, true)
|
40
40
|
declaration(6, true).should be > declaration(5, true)
|
data/spec/lib/roadie_spec.rb
CHANGED
@@ -2,7 +2,7 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Roadie do
|
4
4
|
describe ".inline_css" do
|
5
|
-
it "
|
5
|
+
it "creates an instance of Roadie::Inliner and execute it" do
|
6
6
|
Roadie::Inliner.should_receive(:new).with('attri', 'butes').and_return(double('inliner', :execute => 'html'))
|
7
7
|
Roadie.inline_css('attri', 'butes').should == 'html'
|
8
8
|
end
|
@@ -11,16 +11,16 @@ describe Roadie do
|
|
11
11
|
describe ".load_css(root, targets)" do
|
12
12
|
let(:fixtures_root) { Pathname.new(__FILE__).dirname.join('..', 'fixtures', 'public', 'stylesheets') }
|
13
13
|
|
14
|
-
it "
|
14
|
+
it "loads files matching the target names under root/public/stylesheets" do
|
15
15
|
Roadie.load_css(fixtures_root, ['foo']).should == 'contents of foo'
|
16
16
|
end
|
17
17
|
|
18
|
-
it "
|
18
|
+
it "loads files in order and join them with a newline" do
|
19
19
|
Roadie.load_css(fixtures_root, %w[foo bar]).should == "contents of foo\ncontents of bar"
|
20
20
|
Roadie.load_css(fixtures_root, %w[bar foo]).should == "contents of bar\ncontents of foo"
|
21
21
|
end
|
22
22
|
|
23
|
-
it "
|
23
|
+
it "raises a Roadie::CSSFileNotFound error when a css file could not be found" do
|
24
24
|
expect { Roadie.load_css(fixtures_root, ['not_here']) }.to raise_error(Roadie::CSSFileNotFound, /not_here/)
|
25
25
|
end
|
26
26
|
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: roadie
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 1.0
|
5
|
+
version: 1.1.0
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Magnus Bergmark
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2011-06-
|
13
|
+
date: 2011-06-24 00:00:00 +02:00
|
14
14
|
default_executable:
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
@@ -65,15 +65,17 @@ executables: []
|
|
65
65
|
extensions: []
|
66
66
|
|
67
67
|
extra_rdoc_files:
|
68
|
-
- README.
|
68
|
+
- README.md
|
69
|
+
- Changelog.md
|
69
70
|
files:
|
70
71
|
- .autotest
|
71
72
|
- .gitignore
|
72
73
|
- .yardopts
|
74
|
+
- Changelog.md
|
73
75
|
- Gemfile
|
74
76
|
- Gemfile.lock
|
75
77
|
- MIT-LICENSE
|
76
|
-
- README.
|
78
|
+
- README.md
|
77
79
|
- Rakefile
|
78
80
|
- autotest/discover.rb
|
79
81
|
- lib/roadie.rb
|
@@ -85,7 +87,9 @@ files:
|
|
85
87
|
- roadie.gemspec
|
86
88
|
- spec/fixtures/public/stylesheets/bar.css
|
87
89
|
- spec/fixtures/public/stylesheets/foo.css
|
90
|
+
- spec/fixtures/public/stylesheets/green_paragraphs.css
|
88
91
|
- spec/fixtures/public/stylesheets/integration.css
|
92
|
+
- spec/fixtures/public/stylesheets/large_purple_paragraphs.css
|
89
93
|
- spec/fixtures/views/integration_mailer/marketing.html.erb
|
90
94
|
- spec/fixtures/views/integration_mailer/notification.html.erb
|
91
95
|
- spec/fixtures/views/integration_mailer/notification.text.erb
|
@@ -130,7 +134,9 @@ summary: Making HTML emails comfortable for the Rails rockstars
|
|
130
134
|
test_files:
|
131
135
|
- spec/fixtures/public/stylesheets/bar.css
|
132
136
|
- spec/fixtures/public/stylesheets/foo.css
|
137
|
+
- spec/fixtures/public/stylesheets/green_paragraphs.css
|
133
138
|
- spec/fixtures/public/stylesheets/integration.css
|
139
|
+
- spec/fixtures/public/stylesheets/large_purple_paragraphs.css
|
134
140
|
- spec/fixtures/views/integration_mailer/marketing.html.erb
|
135
141
|
- spec/fixtures/views/integration_mailer/notification.html.erb
|
136
142
|
- spec/fixtures/views/integration_mailer/notification.text.erb
|
data/README.textile
DELETED
@@ -1,114 +0,0 @@
|
|
1
|
-
h1. Roadie
|
2
|
-
|
3
|
-
Roadie tries to make sending HTML emails a little less painful in Rails 3 by inlining stylesheets and rewrite relative URLs for you.
|
4
|
-
|
5
|
-
If you want to have this in Rails 2, please see "MailStyle":https://www.github.com/purify/mail_style.
|
6
|
-
|
7
|
-
h2. How does it work?
|
8
|
-
|
9
|
-
Email clients have bad support for stylesheets, and some of them blocks stylesheets from downloading. The easiest way to handle this is to work with all styles inline, but that is error prone and hard to work with as you cannot use classes and/or reuse styling.
|
10
|
-
|
11
|
-
This gem helps making this easier by automatically inlining stylesheet rules into the document before sending it. You just give it a list of stylesheets and it will go though all of the selectors assigning the styles to the maching elements. Careful attention has been put into rules being applied in the correct order, so it should behave just like in the browser[1].
|
12
|
-
|
13
|
-
Roadie also rewrites all relative URLs in the email to a absolute counterpart, making images you insert and those referenced in your stylesheets work. No more headaches about how to write the stylesheets while still having them work with emails from your acceptance environments.
|
14
|
-
|
15
|
-
fn1. Of course, rules like @:hover@ will not work by definition. Only static styles can be added.
|
16
|
-
|
17
|
-
h2. Features
|
18
|
-
|
19
|
-
* Writes CSS styles inline
|
20
|
-
** Respects @!important@ styles
|
21
|
-
** Does not overwrite styles already present in the @style@ attribute of tags
|
22
|
-
** Supports the same CSS selectors as "Nokogiri":http://nokogiri.org/ (use CSS3 selectors in your emails!)
|
23
|
-
* Makes image urls absolute
|
24
|
-
** Hostname and port configurable on a per-environment basis
|
25
|
-
* Makes link <code>href</code>s absolute
|
26
|
-
* Automatically adds proper html skeleton when missing (you don't have to create a layout for emails)[2]
|
27
|
-
|
28
|
-
fn2. This might be removed in a future version, though. You really ought to create a good layout and not let Roadie guess how you want to have it structured
|
29
|
-
|
30
|
-
h3. What about Sass / Less?
|
31
|
-
|
32
|
-
Sass is supported "by accident" as long as the stylesheets are generated and stored in the stylesheets directory. This is the default behavior from Sass. You are recommended to add a deploy task that generates the stylesheets to make sure that they are present at all times.
|
33
|
-
|
34
|
-
h2. Install
|
35
|
-
|
36
|
-
Roadie is officially supported in both Ruby 1.8.7 and 1.9.2.
|
37
|
-
|
38
|
-
Add the gem to Rails' Gemfile
|
39
|
-
<pre><code>gem 'roadie'</code></pre>
|
40
|
-
|
41
|
-
h2. Usage
|
42
|
-
|
43
|
-
Simply specify the <code>:css</code> option to mailer:
|
44
|
-
|
45
|
-
<pre><code>class Notifier < ActionMailer::Base
|
46
|
-
default :css => :email, :from => 'support@mycompany.com'
|
47
|
-
def registration_mail
|
48
|
-
mail(:subject => 'Welcome Aboard', :to => 'someone@example.com')
|
49
|
-
end
|
50
|
-
|
51
|
-
def newsletter
|
52
|
-
mail(:subject => 'Newsletter', :to => 'someone@example.com', :css => [:email, :newsletter])
|
53
|
-
end
|
54
|
-
end</code></pre>
|
55
|
-
(you could also use the @defaults@ method in ActionMailer)
|
56
|
-
|
57
|
-
This will look for a css file called @email.css@ in your @public/stylesheets@ folder. The @css@ method can take either a string, a symbol or an array of both. You should pass the CSS filename without the ".css" extension.
|
58
|
-
|
59
|
-
h3. Image URL Correcting
|
60
|
-
|
61
|
-
If you have @default_url_options[:host]@ set in your mailer, then Roadie will do it's best to make the urls of images and in stylesheets absolute.
|
62
|
-
|
63
|
-
In @application.rb@:
|
64
|
-
<pre><code>class Application
|
65
|
-
config.action_mailer.default_url_options = {:host => 'example.com'}
|
66
|
-
end</code></pre>
|
67
|
-
|
68
|
-
If you want to to be different depending on your environment, just set it in your environment's configuration instead.
|
69
|
-
|
70
|
-
h3. Ignoring stylesheets
|
71
|
-
|
72
|
-
By default, @style@ elements in the email document's @head@ are processed along with the stylesheets and removed from the @head@.
|
73
|
-
|
74
|
-
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@. This is the place to put things like @:hover@ selectors that you want to have for email clients allowing them.
|
75
|
-
|
76
|
-
Style elements with <code>media="print"</code> are always ignored.
|
77
|
-
|
78
|
-
h2. Documentation
|
79
|
-
|
80
|
-
* "Online documentation for master":http://rubydoc.info/github/Mange/roadie/master/frames
|
81
|
-
|
82
|
-
h2. Bugs / TODO
|
83
|
-
|
84
|
-
* Improve overall performance
|
85
|
-
* Clean up stylesheet assignment code
|
86
|
-
|
87
|
-
h2. History and contributors
|
88
|
-
|
89
|
-
This gem was originally developed for Rails 2 use on "Purify":http://purifyapp.com under the name "MailStyle":https://www.github.com/purify/mail_style. However, the author stopped maintaining it and a fork took place to make it Rails 3 compatible.
|
90
|
-
|
91
|
-
The following people have contributed to the orignal gem:
|
92
|
-
|
93
|
-
* "Jim Neath":http://jimneath.org (Original author)
|
94
|
-
* "Lars Klevans":http://tastybyte.blogspot.com/
|
95
|
-
* "Jonas Grimfelt":http://github.com/grimen
|
96
|
-
* "Ben Johnson":http://www.binarylogic.com
|
97
|
-
* "Istvan Hoka":http://istvanhoka.com/
|
98
|
-
* "Voraz":http://blog.voraz.com.br
|
99
|
-
|
100
|
-
h2. License
|
101
|
-
|
102
|
-
(The MIT License)
|
103
|
-
|
104
|
-
Copyright © 2009-2011
|
105
|
-
|
106
|
-
* "Jim Neath":http://jimneath.org
|
107
|
-
* Magnus Bergmark <magnus.bergmark@gmail.com>
|
108
|
-
|
109
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ‘Software’), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
110
|
-
|
111
|
-
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
112
|
-
|
113
|
-
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.
|
114
|
-
|