pakyow-mailer 0.10.2 → 0.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/pakyow-mailer/CHANGELOG.md +5 -0
- data/pakyow-mailer/lib/pakyow-mailer.rb +1 -11
- data/pakyow-mailer/lib/pakyow/mailer.rb +9 -0
- data/pakyow-mailer/lib/{mailer → pakyow/mailer}/config/mailer.rb +3 -3
- data/pakyow-mailer/lib/pakyow/mailer/ext/premailer/adapter/oga.rb +303 -0
- data/pakyow-mailer/lib/pakyow/mailer/helpers.rb +7 -0
- data/pakyow-mailer/lib/{mailer → pakyow/mailer}/mailer.rb +3 -2
- metadata +14 -13
- data/pakyow-mailer/lib/mailer/helpers.rb +0 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d0b74faa0bb8adc9f4f7016b3b5fc9973c3450a8
|
4
|
+
data.tar.gz: 0ed46bc6a8dc4ac35434b6deb381aea87e7dd3d3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 16c2a60c86fd4f10ce099359b7667412fc691e9c3c234e948619912cc06a6a3de5c58f4df9b65113c1875284dccd624eb2b86d48c9cdeab40832c52c364a65e5
|
7
|
+
data.tar.gz: 505ed535070e22021d6aad5dc4e8a6660a7fde7c2da6015d8871cb202c509c0f45cc0bc1d446284ca43b545a9c4c98759f1aa256e6814a3fdfbd60b7a629b1af
|
data/pakyow-mailer/CHANGELOG.md
CHANGED
@@ -1,11 +1 @@
|
|
1
|
-
|
2
|
-
$LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
|
3
|
-
|
4
|
-
# Gems
|
5
|
-
require 'mail'
|
6
|
-
require 'premailer'
|
7
|
-
|
8
|
-
# Base
|
9
|
-
require 'mailer/mailer'
|
10
|
-
require 'mailer/config/mailer'
|
11
|
-
require 'mailer/helpers'
|
1
|
+
require 'pakyow/mailer'
|
@@ -1,9 +1,9 @@
|
|
1
|
-
Pakyow::Config.register
|
1
|
+
Pakyow::Config.register :mailer do |config|
|
2
2
|
# the default sender name
|
3
3
|
config.opt :default_sender, 'Pakyow'
|
4
4
|
|
5
5
|
# the default mimetype to use
|
6
|
-
config.opt :default_content_type,
|
6
|
+
config.opt :default_content_type, -> { 'text/html; charset=' + Pakyow::Config.mailer.encoding }
|
7
7
|
|
8
8
|
# the delivery method to use for sending mail
|
9
9
|
config.opt :delivery_method, :sendmail
|
@@ -13,4 +13,4 @@ Pakyow::Config.register(:mailer) { |config|
|
|
13
13
|
|
14
14
|
# the default encoding to use
|
15
15
|
config.opt :encoding, 'UTF-8'
|
16
|
-
|
16
|
+
end
|
@@ -0,0 +1,303 @@
|
|
1
|
+
require 'oga'
|
2
|
+
|
3
|
+
module Pakyow
|
4
|
+
class OgaAttributes
|
5
|
+
def initialize(attributes)
|
6
|
+
@attributes = attributes
|
7
|
+
end
|
8
|
+
|
9
|
+
def [](key)
|
10
|
+
if key.is_a?(Integer)
|
11
|
+
super
|
12
|
+
else
|
13
|
+
@attributes.find { |a| a.name.to_sym == key.to_sym }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def method_missing(*args, &block)
|
18
|
+
@attributes.send(*args, &block)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
module Oga
|
24
|
+
module XML
|
25
|
+
class Document
|
26
|
+
# Premailer builds its css searches with `@` prepended to an attribute,
|
27
|
+
# which breaks Oga. Since Premailer also expects `search` to be defined
|
28
|
+
# on a document, we define it and just strip the `@` out before calling
|
29
|
+
# Oga's build-in #css method.
|
30
|
+
def search(query)
|
31
|
+
css(query.gsub('@', ''))
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class Element
|
36
|
+
def attributes
|
37
|
+
Pakyow::OgaAttributes.new(@attributes)
|
38
|
+
end
|
39
|
+
|
40
|
+
def []=(name, value)
|
41
|
+
set(name, value)
|
42
|
+
end
|
43
|
+
|
44
|
+
def [](name)
|
45
|
+
get(name)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class Attribute
|
50
|
+
def to_str
|
51
|
+
value
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class Premailer
|
58
|
+
# This method was tied to a specific type of document rather than being
|
59
|
+
# adapter-agnostic (honestly it's a stretch to call them adapters). So,
|
60
|
+
# we just pulled this method in and made it work w/ Oga.
|
61
|
+
def load_css_from_html! # :nodoc:
|
62
|
+
tags = @doc.css("link[rel='stylesheet']").reject { |tag|
|
63
|
+
tag.attribute('data-premailer') == 'ignore'
|
64
|
+
}
|
65
|
+
|
66
|
+
if tags
|
67
|
+
tags.each do |tag|
|
68
|
+
if tag.to_s.strip =~ /^\<link/i && tag.attribute('href') && media_type_ok?(tag.attribute('media')) && @options[:include_link_tags]
|
69
|
+
# A user might want to <link /> to a local css file that is also mirrored on the site
|
70
|
+
# but the local one is different (e.g. newer) than the live file, premailer will now choose the local file
|
71
|
+
|
72
|
+
if tag.attribute('href').to_s.include? @base_url.to_s and @html_file.kind_of?(String)
|
73
|
+
if @options[:with_html_string]
|
74
|
+
link_uri = tag.attribute('href').to_s.sub(@base_url.to_s, '')
|
75
|
+
else
|
76
|
+
link_uri = File.join(File.dirname(@html_file), tag.attribute('href').to_s.sub!(@base_url.to_s, ''))
|
77
|
+
# if the file does not exist locally, try to grab the remote reference
|
78
|
+
unless File.exists?(link_uri)
|
79
|
+
link_uri = Premailer.resolve_link(tag.attribute('href').to_s, @html_file)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
else
|
83
|
+
link_uri = tag.attribute('href').to_s
|
84
|
+
end
|
85
|
+
|
86
|
+
if Premailer.local_data?(link_uri)
|
87
|
+
$stderr.puts "Loading css from local file: " + link_uri if @options[:verbose]
|
88
|
+
load_css_from_local_file!(link_uri)
|
89
|
+
else
|
90
|
+
$stderr.puts "Loading css from uri: " + link_uri if @options[:verbose]
|
91
|
+
@css_parser.load_uri!(link_uri, {:only_media_types => [:screen, :handheld]})
|
92
|
+
end
|
93
|
+
|
94
|
+
elsif tag.to_s.strip =~ /^\<style/i && @options[:include_style_tags]
|
95
|
+
@css_parser.add_block!(tag.inner_html, :base_uri => @base_url, :base_dir => @base_dir, :only_media_types => [:screen, :handheld])
|
96
|
+
end
|
97
|
+
end
|
98
|
+
unless @options[:preserve_styles]
|
99
|
+
tags.each do |tag|
|
100
|
+
tag.remove
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
module Adapter
|
107
|
+
# Oga adapter
|
108
|
+
module Oga
|
109
|
+
|
110
|
+
# Merge CSS into the HTML document.
|
111
|
+
#
|
112
|
+
# @return [String] an HTML.
|
113
|
+
def to_inline_css
|
114
|
+
doc = @processed_doc
|
115
|
+
@unmergable_rules = CssParser::Parser.new
|
116
|
+
|
117
|
+
# Give all styles already in style attributes a specificity of 1000
|
118
|
+
# per http://www.w3.org/TR/CSS21/cascade.html#specificity
|
119
|
+
doc.css("*[style]").each do |el|
|
120
|
+
el['style'] = '[SPEC=1000[' + el.attribute('style') + ']]'
|
121
|
+
end
|
122
|
+
# Iterate through the rules and merge them into the HTML
|
123
|
+
@css_parser.each_selector(:all) do |selector, declaration, specificity, media_types|
|
124
|
+
# Save un-mergable rules separately
|
125
|
+
selector.gsub!(/:link([\s]*)+/i) { |m| $1 }
|
126
|
+
|
127
|
+
# Convert element names to lower case
|
128
|
+
selector.gsub!(/([\s]|^)([\w]+)/) { |m| $1.to_s + $2.to_s.downcase }
|
129
|
+
|
130
|
+
if Premailer.is_media_query?(media_types) || selector =~ Premailer::RE_UNMERGABLE_SELECTORS
|
131
|
+
@unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selector, declaration), media_types) unless @options[:preserve_styles]
|
132
|
+
else
|
133
|
+
begin
|
134
|
+
if selector =~ Premailer::RE_RESET_SELECTORS
|
135
|
+
# this is in place to preserve the MailChimp CSS reset: http://github.com/mailchimp/Email-Blueprints/
|
136
|
+
# however, this doesn't mean for testing pur
|
137
|
+
@unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selector, declaration)) unless !@options[:preserve_reset]
|
138
|
+
end
|
139
|
+
|
140
|
+
# Change single ID CSS selectors into xpath so that we can match more
|
141
|
+
# than one element. Added to work around dodgy generated code.
|
142
|
+
selector.gsub!(/\A\#([\w_\-]+)\Z/, '*[@id=\1]')
|
143
|
+
|
144
|
+
doc.css(selector).each do |el|
|
145
|
+
if el.elem? and (el.name != 'head' and el.parent.name != 'head')
|
146
|
+
# Add a style attribute or append to the existing one
|
147
|
+
block = "[SPEC=#{specificity}[#{declaration}]]"
|
148
|
+
el['style'] = (el.attribute('style').to_s ||= '') + ' ' + block
|
149
|
+
end
|
150
|
+
end
|
151
|
+
rescue RuntimeError, ArgumentError
|
152
|
+
$stderr.puts "CSS syntax error with selector: #{selector}" if @options[:verbose]
|
153
|
+
next
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# Remove script tags
|
159
|
+
if @options[:remove_scripts]
|
160
|
+
doc.css("script").remove
|
161
|
+
end
|
162
|
+
|
163
|
+
# Read STYLE attributes and perform folding
|
164
|
+
doc.css("*[style]").each do |el|
|
165
|
+
style = el.attribute('style').to_s
|
166
|
+
|
167
|
+
declarations = []
|
168
|
+
style.scan(/\[SPEC\=([\d]+)\[(.[^\]\]]*)\]\]/).each do |declaration|
|
169
|
+
rs = CssParser::RuleSet.new(nil, declaration[1].to_s, declaration[0].to_i)
|
170
|
+
declarations << rs
|
171
|
+
end
|
172
|
+
|
173
|
+
# Perform style folding
|
174
|
+
merged = CssParser.merge(declarations)
|
175
|
+
merged.expand_shorthand!
|
176
|
+
|
177
|
+
# Duplicate CSS attributes as HTML attributes
|
178
|
+
if Premailer::RELATED_ATTRIBUTES.has_key?(el.name) && @options[:css_to_attributes]
|
179
|
+
Premailer::RELATED_ATTRIBUTES[el.name].each do |css_att, html_att|
|
180
|
+
el[html_att] = merged[css_att].gsub(/url\(['|"](.*)['|"]\)/, '\1').gsub(/;$|\s*!important/, '').strip if el[html_att].nil? and not merged[css_att].empty?
|
181
|
+
merged.instance_variable_get("@declarations").tap do |declarations|
|
182
|
+
declarations.delete(css_att)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
# Collapse multiple rules into one as much as possible.
|
187
|
+
merged.create_shorthand! if @options[:create_shorthands]
|
188
|
+
|
189
|
+
# write the inline STYLE attribute
|
190
|
+
attributes = Premailer.escape_string(merged.declarations_to_s).split(';').map(&:strip)
|
191
|
+
attributes = attributes.map { |attr| [attr.split(':').first, attr] }.sort_by { |pair| pair.first }.map { |pair| pair[1] }
|
192
|
+
el['style'] = attributes.join('; ')
|
193
|
+
end
|
194
|
+
|
195
|
+
doc = write_unmergable_css_rules(doc, @unmergable_rules)
|
196
|
+
|
197
|
+
if @options[:remove_classes] or @options[:remove_comments]
|
198
|
+
doc.traverse do |el|
|
199
|
+
if el.comment? and @options[:remove_comments]
|
200
|
+
el.remove
|
201
|
+
elsif el.element?
|
202
|
+
el.unset('class') if @options[:remove_classes]
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
if @options[:remove_ids]
|
208
|
+
# find all anchor's targets and hash them
|
209
|
+
targets = []
|
210
|
+
doc.css("a[@href^='#']").each do |el|
|
211
|
+
target = el.get_attribute('href')[1..-1]
|
212
|
+
targets << target
|
213
|
+
el.set('href', "#" + Digest::MD5.hexdigest(target))
|
214
|
+
end
|
215
|
+
# hash ids that are links target, delete others
|
216
|
+
doc.css("*[@id]").each do |el|
|
217
|
+
id = el.get_attribute('id')
|
218
|
+
if targets.include?(id)
|
219
|
+
el.set('id', Digest::MD5.hexdigest(id))
|
220
|
+
else
|
221
|
+
el.unset('id')
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
if @options[:reset_contenteditable]
|
227
|
+
doc.css('*[contenteditable]').each do |el|
|
228
|
+
el.unset('contenteditable')
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
@processed_doc = doc
|
233
|
+
@processed_doc.to_xml
|
234
|
+
end
|
235
|
+
|
236
|
+
# Create a <tt>style</tt> element with un-mergable rules (e.g. <tt>:hover</tt>)
|
237
|
+
# and write it into the <tt>body</tt>.
|
238
|
+
#
|
239
|
+
# <tt>doc</tt> is an Oga document and <tt>unmergable_css_rules</tt> is a Css::RuleSet.
|
240
|
+
#
|
241
|
+
# @return [::Oga::XML::Document] a document.
|
242
|
+
def write_unmergable_css_rules(doc, unmergable_rules) # :nodoc:
|
243
|
+
styles = unmergable_rules.to_s
|
244
|
+
|
245
|
+
unless styles.empty?
|
246
|
+
style_tag = "<style type=\"text/css\">\n#{styles}</style>"
|
247
|
+
unless (body = doc.css('body')).empty?
|
248
|
+
if doc.at_css('body').children && !doc.at_css('body').children.empty?
|
249
|
+
doc.at_css('body').children.before(::Oga.parse_html(style_tag))
|
250
|
+
else
|
251
|
+
doc.at_css('body').add_child(::Oga.parse_html(style_tag))
|
252
|
+
end
|
253
|
+
else
|
254
|
+
doc.inner_html = style_tag += doc.inner_html
|
255
|
+
end
|
256
|
+
end
|
257
|
+
doc
|
258
|
+
end
|
259
|
+
|
260
|
+
|
261
|
+
# Converts the HTML document to a format suitable for plain-text e-mail.
|
262
|
+
#
|
263
|
+
# If present, uses the <body> element as its base; otherwise uses the whole document.
|
264
|
+
#
|
265
|
+
# @return [String] a plain text.
|
266
|
+
def to_plain_text
|
267
|
+
html_src = ''
|
268
|
+
begin
|
269
|
+
html_src = @doc.at("body").inner_html
|
270
|
+
rescue;
|
271
|
+
end
|
272
|
+
|
273
|
+
html_src = @doc.to_xml unless html_src and not html_src.empty?
|
274
|
+
convert_to_text(html_src, @options[:line_length], @html_encoding)
|
275
|
+
end
|
276
|
+
|
277
|
+
# Gets the original HTML as a string.
|
278
|
+
# @return [String] HTML.
|
279
|
+
def to_s
|
280
|
+
@doc.to_xml
|
281
|
+
end
|
282
|
+
|
283
|
+
# Load the HTML file and convert it into an Oga document.
|
284
|
+
#
|
285
|
+
# @return [::Oga::XML::Document] a document.
|
286
|
+
def load_html(input) # :nodoc:
|
287
|
+
thing = nil
|
288
|
+
|
289
|
+
# TODO: duplicate options
|
290
|
+
if @options[:with_html_string] or @options[:inline] or input.respond_to?(:read)
|
291
|
+
thing = input
|
292
|
+
elsif @is_local_file
|
293
|
+
@base_dir = File.dirname(input)
|
294
|
+
thing = File.open(input, 'r')
|
295
|
+
else
|
296
|
+
thing = open(input)
|
297
|
+
end
|
298
|
+
|
299
|
+
::Oga.parse_html(thing)
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
@@ -2,8 +2,9 @@ module Pakyow
|
|
2
2
|
class Mailer
|
3
3
|
attr_accessor :view, :message, :processed
|
4
4
|
|
5
|
-
def self.from_store(view_path, view_store)
|
6
|
-
|
5
|
+
def self.from_store(view_path, view_store, context = nil)
|
6
|
+
view = view_store.view(view_path)
|
7
|
+
new(view: Pakyow::Presenter::ViewContext.new(view, context))
|
7
8
|
end
|
8
9
|
|
9
10
|
def initialize(view: nil, content: nil)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pakyow-mailer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.11.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bryan Powell
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2016-04-21 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: pakyow-support
|
@@ -17,42 +17,42 @@ dependencies:
|
|
17
17
|
requirements:
|
18
18
|
- - '='
|
19
19
|
- !ruby/object:Gem::Version
|
20
|
-
version: 0.
|
20
|
+
version: 0.11.0
|
21
21
|
type: :runtime
|
22
22
|
prerelease: false
|
23
23
|
version_requirements: !ruby/object:Gem::Requirement
|
24
24
|
requirements:
|
25
25
|
- - '='
|
26
26
|
- !ruby/object:Gem::Version
|
27
|
-
version: 0.
|
27
|
+
version: 0.11.0
|
28
28
|
- !ruby/object:Gem::Dependency
|
29
29
|
name: pakyow-core
|
30
30
|
requirement: !ruby/object:Gem::Requirement
|
31
31
|
requirements:
|
32
32
|
- - '='
|
33
33
|
- !ruby/object:Gem::Version
|
34
|
-
version: 0.
|
34
|
+
version: 0.11.0
|
35
35
|
type: :runtime
|
36
36
|
prerelease: false
|
37
37
|
version_requirements: !ruby/object:Gem::Requirement
|
38
38
|
requirements:
|
39
39
|
- - '='
|
40
40
|
- !ruby/object:Gem::Version
|
41
|
-
version: 0.
|
41
|
+
version: 0.11.0
|
42
42
|
- !ruby/object:Gem::Dependency
|
43
43
|
name: pakyow-presenter
|
44
44
|
requirement: !ruby/object:Gem::Requirement
|
45
45
|
requirements:
|
46
46
|
- - '='
|
47
47
|
- !ruby/object:Gem::Version
|
48
|
-
version: 0.
|
48
|
+
version: 0.11.0
|
49
49
|
type: :runtime
|
50
50
|
prerelease: false
|
51
51
|
version_requirements: !ruby/object:Gem::Requirement
|
52
52
|
requirements:
|
53
53
|
- - '='
|
54
54
|
- !ruby/object:Gem::Version
|
55
|
-
version: 0.
|
55
|
+
version: 0.11.0
|
56
56
|
- !ruby/object:Gem::Dependency
|
57
57
|
name: mail
|
58
58
|
requirement: !ruby/object:Gem::Requirement
|
@@ -104,10 +104,12 @@ files:
|
|
104
104
|
- pakyow-mailer/CHANGELOG.md
|
105
105
|
- pakyow-mailer/LICENSE
|
106
106
|
- pakyow-mailer/README.md
|
107
|
-
- pakyow-mailer/lib/mailer/config/mailer.rb
|
108
|
-
- pakyow-mailer/lib/mailer/helpers.rb
|
109
|
-
- pakyow-mailer/lib/mailer/mailer.rb
|
110
107
|
- pakyow-mailer/lib/pakyow-mailer.rb
|
108
|
+
- pakyow-mailer/lib/pakyow/mailer.rb
|
109
|
+
- pakyow-mailer/lib/pakyow/mailer/config/mailer.rb
|
110
|
+
- pakyow-mailer/lib/pakyow/mailer/ext/premailer/adapter/oga.rb
|
111
|
+
- pakyow-mailer/lib/pakyow/mailer/helpers.rb
|
112
|
+
- pakyow-mailer/lib/pakyow/mailer/mailer.rb
|
111
113
|
homepage: http://pakyow.org
|
112
114
|
licenses:
|
113
115
|
- MIT
|
@@ -128,9 +130,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
128
130
|
version: '0'
|
129
131
|
requirements: []
|
130
132
|
rubyforge_project:
|
131
|
-
rubygems_version: 2.
|
133
|
+
rubygems_version: 2.5.1
|
132
134
|
signing_key:
|
133
135
|
specification_version: 4
|
134
136
|
summary: Pakyow Mailer
|
135
137
|
test_files: []
|
136
|
-
has_rdoc:
|