pakyow-mailer 0.10.2 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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:
|