inkcite 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +20 -0
- data/README.md +110 -0
- data/Rakefile +8 -0
- data/assets/facebook-like.css +62 -0
- data/assets/facebook-like.js +59 -0
- data/assets/init/config.yml +97 -0
- data/assets/init/helpers.tsv +31 -0
- data/assets/init/source.html +60 -0
- data/assets/init/source.txt +6 -0
- data/bin/inkcite +6 -0
- data/inkcite.gemspec +42 -0
- data/lib/inkcite.rb +32 -0
- data/lib/inkcite/cli/base.rb +128 -0
- data/lib/inkcite/cli/build.rb +130 -0
- data/lib/inkcite/cli/init.rb +58 -0
- data/lib/inkcite/cli/preview.rb +30 -0
- data/lib/inkcite/cli/server.rb +123 -0
- data/lib/inkcite/cli/test.rb +61 -0
- data/lib/inkcite/email.rb +219 -0
- data/lib/inkcite/mailer.rb +140 -0
- data/lib/inkcite/minifier.rb +151 -0
- data/lib/inkcite/parser.rb +111 -0
- data/lib/inkcite/renderer.rb +177 -0
- data/lib/inkcite/renderer/base.rb +186 -0
- data/lib/inkcite/renderer/button.rb +168 -0
- data/lib/inkcite/renderer/div.rb +29 -0
- data/lib/inkcite/renderer/element.rb +82 -0
- data/lib/inkcite/renderer/footnote.rb +132 -0
- data/lib/inkcite/renderer/google_analytics.rb +35 -0
- data/lib/inkcite/renderer/image.rb +95 -0
- data/lib/inkcite/renderer/image_base.rb +82 -0
- data/lib/inkcite/renderer/in_browser.rb +38 -0
- data/lib/inkcite/renderer/like.rb +73 -0
- data/lib/inkcite/renderer/link.rb +243 -0
- data/lib/inkcite/renderer/litmus.rb +33 -0
- data/lib/inkcite/renderer/lorem.rb +39 -0
- data/lib/inkcite/renderer/mobile_image.rb +67 -0
- data/lib/inkcite/renderer/mobile_style.rb +40 -0
- data/lib/inkcite/renderer/mobile_toggle.rb +27 -0
- data/lib/inkcite/renderer/outlook_background.rb +48 -0
- data/lib/inkcite/renderer/partial.rb +31 -0
- data/lib/inkcite/renderer/preheader.rb +22 -0
- data/lib/inkcite/renderer/property.rb +39 -0
- data/lib/inkcite/renderer/responsive.rb +334 -0
- data/lib/inkcite/renderer/span.rb +21 -0
- data/lib/inkcite/renderer/table.rb +67 -0
- data/lib/inkcite/renderer/table_base.rb +149 -0
- data/lib/inkcite/renderer/td.rb +92 -0
- data/lib/inkcite/uploader.rb +173 -0
- data/lib/inkcite/util.rb +85 -0
- data/lib/inkcite/version.rb +3 -0
- data/lib/inkcite/view.rb +745 -0
- data/lib/inkcite/view/context.rb +38 -0
- data/lib/inkcite/view/media_query.rb +60 -0
- data/lib/inkcite/view/tag_stack.rb +38 -0
- data/test/email_spec.rb +16 -0
- data/test/parser_spec.rb +72 -0
- data/test/project/config.yml +98 -0
- data/test/project/helpers.tsv +56 -0
- data/test/project/images/inkcite.jpg +0 -0
- data/test/project/source.html +58 -0
- data/test/project/source.txt +6 -0
- data/test/renderer/button_spec.rb +45 -0
- data/test/renderer/div_spec.rb +101 -0
- data/test/renderer/element_spec.rb +31 -0
- data/test/renderer/footnote_spec.rb +57 -0
- data/test/renderer/image_spec.rb +82 -0
- data/test/renderer/link_spec.rb +84 -0
- data/test/renderer/mobile_image_spec.rb +27 -0
- data/test/renderer/mobile_style_spec.rb +37 -0
- data/test/renderer/td_spec.rb +126 -0
- data/test/renderer_spec.rb +28 -0
- data/test/view_spec.rb +15 -0
- metadata +333 -0
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'litmus'
|
2
|
+
require 'inkcite/mailer'
|
3
|
+
|
4
|
+
module Inkcite
|
5
|
+
module Cli
|
6
|
+
class Test
|
7
|
+
|
8
|
+
def self.invoke email, opt
|
9
|
+
|
10
|
+
# Push the browser preview up to the server to ensure that the
|
11
|
+
# latest images are available.
|
12
|
+
email.upload
|
13
|
+
|
14
|
+
config = email.config[:litmus]
|
15
|
+
|
16
|
+
# Initialize the Litmus base.
|
17
|
+
Litmus::Base.new(config[:subdomain], config[:username], config[:password], true)
|
18
|
+
|
19
|
+
# Send each version to Litmus separately
|
20
|
+
email.versions.each do |version|
|
21
|
+
|
22
|
+
view = email.view(:preview, :email, version)
|
23
|
+
|
24
|
+
# This will hold the Litmus Test Version which provides the GUID (e.g. email)
|
25
|
+
# to which we will send.
|
26
|
+
test_version = nil
|
27
|
+
|
28
|
+
# Check to see if this email already has a test ID.
|
29
|
+
test_id = view.meta(:litmus_test_id)
|
30
|
+
if test_id.blank? || opt[:new]
|
31
|
+
|
32
|
+
email_test = Litmus::EmailTest.create
|
33
|
+
|
34
|
+
# Store the litmus test ID in the email's meta data.
|
35
|
+
view.set_meta :litmus_test_id, email_test['id']
|
36
|
+
|
37
|
+
# Extract the email address we need to send the test to.
|
38
|
+
test_version = email_test["test_set_versions"].first
|
39
|
+
|
40
|
+
else
|
41
|
+
|
42
|
+
# Create a new version of the test using the same ID as before.
|
43
|
+
test_version = Litmus::TestVersion.create(test_id)
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
# Extract the email address to send the test to.
|
48
|
+
send_to = test_version["url_or_guid"]
|
49
|
+
|
50
|
+
puts "Sending '#{view.subject}' to #{send_to} ..."
|
51
|
+
|
52
|
+
Inkcite::Mailer.litmus(email, version, send_to)
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
true
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,219 @@
|
|
1
|
+
module Inkcite
|
2
|
+
class Email
|
3
|
+
|
4
|
+
CACHE_BUST = :'cache-bust'
|
5
|
+
IMAGE_HOST = :'image-host'
|
6
|
+
IMAGE_PLACEHOLDERS = :'image-placeholders'
|
7
|
+
OPTIMIZE_IMAGES = :'optimize-images'
|
8
|
+
TRACK_LINKS = :'track-links'
|
9
|
+
VIEW_IN_BROWSER_URL = :'view-in-browser-url'
|
10
|
+
|
11
|
+
# Sub-directory where images are located.
|
12
|
+
IMAGES = 'images'
|
13
|
+
|
14
|
+
# Allowed environments.
|
15
|
+
ENVIRONMENTS = [ :development, :preview, :production ].freeze
|
16
|
+
|
17
|
+
# The path to the directory from which the email is being generated.
|
18
|
+
# e.g. /projects/emails/holiday-mailing
|
19
|
+
attr_reader :path
|
20
|
+
|
21
|
+
def initialize path
|
22
|
+
@path = path
|
23
|
+
end
|
24
|
+
|
25
|
+
def config
|
26
|
+
Util.read_yml(File.join(path, 'config.yml'), true)
|
27
|
+
end
|
28
|
+
|
29
|
+
def formats env
|
30
|
+
|
31
|
+
f = [ :email, :browser ]
|
32
|
+
|
33
|
+
# Need to make sure a source.txt exists before we can include
|
34
|
+
# it in the list of known formats.
|
35
|
+
f << :text if File.exists?(project_file('source.txt'))
|
36
|
+
|
37
|
+
f
|
38
|
+
end
|
39
|
+
|
40
|
+
def image_dir
|
41
|
+
File.join(path, IMAGES)
|
42
|
+
end
|
43
|
+
|
44
|
+
def image_path file
|
45
|
+
File.join(image_dir, file)
|
46
|
+
end
|
47
|
+
|
48
|
+
def meta key
|
49
|
+
meta_data[key.to_sym]
|
50
|
+
end
|
51
|
+
|
52
|
+
def optimize_images
|
53
|
+
Minifier.images(self)
|
54
|
+
end
|
55
|
+
|
56
|
+
def optimize_images?
|
57
|
+
config[Inkcite::Email::OPTIMIZE_IMAGES] == true
|
58
|
+
end
|
59
|
+
|
60
|
+
# Returns the directory that optimized, compressed images
|
61
|
+
# have been saved to.
|
62
|
+
def optimized_image_dir
|
63
|
+
File.join(path, optimize_images?? Minifier::IMAGE_CACHE : IMAGES)
|
64
|
+
end
|
65
|
+
|
66
|
+
def properties
|
67
|
+
|
68
|
+
opts = {
|
69
|
+
:n => NEW_LINE
|
70
|
+
}
|
71
|
+
|
72
|
+
# Load the project's properties, which may include references to additional
|
73
|
+
# properties in other directories.
|
74
|
+
read_properties opts, 'helpers.tsv'
|
75
|
+
|
76
|
+
# As a convenience pre-populate the month name of the email.
|
77
|
+
mm = opts[:mm].to_i
|
78
|
+
opts[:month] = Date::MONTHNAMES[mm] if mm > 0
|
79
|
+
|
80
|
+
opts
|
81
|
+
end
|
82
|
+
|
83
|
+
def project_file file
|
84
|
+
File.join(path, file)
|
85
|
+
end
|
86
|
+
|
87
|
+
def set_meta key, value
|
88
|
+
md = meta_data
|
89
|
+
md[key.to_sym] = value
|
90
|
+
File.open(File.join(path, meta_file_name), 'w+') { |f| f.write(md.to_yaml) }
|
91
|
+
value
|
92
|
+
end
|
93
|
+
|
94
|
+
def upload
|
95
|
+
require_relative 'uploader'
|
96
|
+
Uploader.upload(self)
|
97
|
+
end
|
98
|
+
|
99
|
+
def upload!
|
100
|
+
require_relative 'uploader'
|
101
|
+
Uploader.upload!(self)
|
102
|
+
end
|
103
|
+
|
104
|
+
def versions
|
105
|
+
[* self.config[:versions] || :default ].collect(&:to_sym)
|
106
|
+
end
|
107
|
+
|
108
|
+
def view environment, format, version=nil
|
109
|
+
|
110
|
+
environment = environment.to_sym
|
111
|
+
format = format.to_sym
|
112
|
+
version = (version || versions.first).to_sym
|
113
|
+
|
114
|
+
raise "Unknown environment \"#{environment}\" - must be one of #{ENVIRONMENTS.join(',')}" unless ENVIRONMENTS.include?(environment)
|
115
|
+
raise "Unknown format \"#{format}\" - must be one of #{FORMATS.join(',')}" unless FORMATS.include?(format)
|
116
|
+
raise "Unknown version: \"#{version}\" - must be one of #{versions.join(',')}" unless versions.include?(version)
|
117
|
+
|
118
|
+
opt = properties
|
119
|
+
opt.merge!(self.config)
|
120
|
+
|
121
|
+
# Instantiate a new view of this email with the desired view and
|
122
|
+
# format.
|
123
|
+
View.new(self, environment, format, version, opt)
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
# Returns an array of all possible Views (every combination of version
|
128
|
+
# and format )of this email for the designated environment.
|
129
|
+
def views environment, &block
|
130
|
+
|
131
|
+
vs = []
|
132
|
+
|
133
|
+
formats(environment).each do |format|
|
134
|
+
versions.each do |version|
|
135
|
+
ev = view(environment, format, version)
|
136
|
+
yield(ev) if block_given?
|
137
|
+
vs << ev
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
vs
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
# Allowed formats.
|
147
|
+
FORMATS = [ :browser, :email, :text ].freeze
|
148
|
+
|
149
|
+
# Name of the property controlling the meta data file name and
|
150
|
+
# the default file name.
|
151
|
+
META_FILE_NAME = :'meta-file'
|
152
|
+
META_FILE = '.inkcite'
|
153
|
+
|
154
|
+
COMMENT = '//'
|
155
|
+
NEW_LINE = "\n"
|
156
|
+
TAB = "\t"
|
157
|
+
CARRIAGE_RETURN = "\r"
|
158
|
+
|
159
|
+
# Used for
|
160
|
+
MULTILINE_START = "<<-START"
|
161
|
+
MULTILINE_END = "END->>"
|
162
|
+
TAB_TO_SPACE = ' '
|
163
|
+
|
164
|
+
def meta_data
|
165
|
+
Util.read_yml(File.join(path, meta_file_name), false)
|
166
|
+
end
|
167
|
+
|
168
|
+
def meta_file_name
|
169
|
+
config[META_FILE_NAME] || META_FILE
|
170
|
+
end
|
171
|
+
|
172
|
+
def read_properties into, file
|
173
|
+
|
174
|
+
fp = File.join(path, file)
|
175
|
+
abort("Can't find #{file} in #{path} - are you sure this is an Inkcite project?") unless File.exists?(fp)
|
176
|
+
|
177
|
+
# Consolidate line-breaks for simplicity
|
178
|
+
raw = File.read(fp)
|
179
|
+
raw.gsub!(/[\r\f\n]{1,}/, NEW_LINE)
|
180
|
+
|
181
|
+
# Initial position of the
|
182
|
+
multiline_starts_at = 0
|
183
|
+
|
184
|
+
# Determine if there are any multiline declarations - those that are wrapped with
|
185
|
+
# <<-START and END->> and reduce them to single line declarations.
|
186
|
+
while (multiline_starts_at = raw.index(MULTILINE_START, multiline_starts_at))
|
187
|
+
|
188
|
+
break unless (multiline_ends_at = raw.index(MULTILINE_END, multiline_starts_at))
|
189
|
+
|
190
|
+
declaration = raw[(multiline_starts_at+MULTILINE_START.length)..multiline_ends_at - 1]
|
191
|
+
declaration.strip!
|
192
|
+
declaration.gsub!(/\t/, TAB_TO_SPACE)
|
193
|
+
declaration.gsub!(/\n/, "\r")
|
194
|
+
|
195
|
+
raw[multiline_starts_at..multiline_ends_at+MULTILINE_END.length - 1] = declaration
|
196
|
+
|
197
|
+
end
|
198
|
+
|
199
|
+
raw.split(NEW_LINE).each do |line|
|
200
|
+
next if line.starts_with?(COMMENT)
|
201
|
+
|
202
|
+
line.gsub!(/\r/, NEW_LINE)
|
203
|
+
line.strip!
|
204
|
+
|
205
|
+
key, open, close = line.split(TAB)
|
206
|
+
next if key.blank?
|
207
|
+
|
208
|
+
into[key.to_sym] = open.to_s.freeze
|
209
|
+
|
210
|
+
# Prepend the key with a "/" and populate the closing tag.
|
211
|
+
into["/#{key}".to_sym] = close.to_s.freeze
|
212
|
+
|
213
|
+
end
|
214
|
+
|
215
|
+
true
|
216
|
+
end
|
217
|
+
|
218
|
+
end
|
219
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
require 'mail'
|
2
|
+
|
3
|
+
module Inkcite
|
4
|
+
class Mailer
|
5
|
+
|
6
|
+
def self.client email
|
7
|
+
|
8
|
+
# Determine which preview this is
|
9
|
+
count = increment(email, :preview)
|
10
|
+
|
11
|
+
# Get the declared set of recipients.
|
12
|
+
recipients = email.config[:recipients]
|
13
|
+
|
14
|
+
# Get the list of client address(es) - check both client and clients
|
15
|
+
# as a convenience.
|
16
|
+
to = recipients[:clients] || recipients[:client]
|
17
|
+
|
18
|
+
# If this is the first preview, check to see if there is an
|
19
|
+
# additional set of recipients for this initial mailing.
|
20
|
+
if count == 1
|
21
|
+
also_to = recipients[FIRST_PREVIEW]
|
22
|
+
#to = [* to] + [* also_to] unless also_to.blank?
|
23
|
+
to = [* also_to] unless also_to.blank?
|
24
|
+
end
|
25
|
+
|
26
|
+
# Always cc internal recipients so everyone stays informed of feedback.
|
27
|
+
cc = recipients[:internal]
|
28
|
+
|
29
|
+
self.send(email, {
|
30
|
+
:to => to,
|
31
|
+
:cc => cc,
|
32
|
+
:bcc => true,
|
33
|
+
:tag => "Preview ##{count}"
|
34
|
+
})
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.developer email
|
39
|
+
|
40
|
+
count = increment(email, :developer)
|
41
|
+
|
42
|
+
self.send(email, {
|
43
|
+
:tag => "Developer Test ##{count}"
|
44
|
+
})
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.litmus email, version, litmus_email
|
49
|
+
self.send_version(email, version, { :to => litmus_email })
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.internal email
|
53
|
+
|
54
|
+
recipients = email.config[:recipients]
|
55
|
+
|
56
|
+
# Determine which preview this is
|
57
|
+
count = increment(email, :internal)
|
58
|
+
|
59
|
+
self.send(email, {
|
60
|
+
:to => recipients[:internal],
|
61
|
+
:bcc => true,
|
62
|
+
:tag => "Internal Proof ##{count}"
|
63
|
+
})
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
# Name of the distribution list used on the first preview. For one
|
70
|
+
# client, they wanted the first preview sent to additional people
|
71
|
+
# but subsequent previews went to a shorter list.
|
72
|
+
FIRST_PREVIEW = :'first-preview'
|
73
|
+
|
74
|
+
def self.increment email, sym
|
75
|
+
count = email.meta(sym).to_i + 1
|
76
|
+
email.set_meta sym, count
|
77
|
+
end
|
78
|
+
|
79
|
+
# Sends each version of the provided email with the indicated options.
|
80
|
+
def self.send email, opt
|
81
|
+
|
82
|
+
email.versions.each do |version|
|
83
|
+
self.send_version(email, version, opt)
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.send_version email, version, opt
|
89
|
+
|
90
|
+
config = email.config[:smtp]
|
91
|
+
|
92
|
+
Mail.defaults do
|
93
|
+
delivery_method :smtp, {
|
94
|
+
:address => config[:host],
|
95
|
+
:port => config[:port],
|
96
|
+
:user_name => config[:username],
|
97
|
+
:password => config[:password],
|
98
|
+
:authentication => :plain,
|
99
|
+
:enable_starttls_auto => true
|
100
|
+
}
|
101
|
+
end
|
102
|
+
|
103
|
+
# The address of the developer
|
104
|
+
_from = config[:from]
|
105
|
+
|
106
|
+
# Subject line tag such as "Preview #3"
|
107
|
+
_tag = opt[:tag]
|
108
|
+
|
109
|
+
# True if the developer should be bcc'd.
|
110
|
+
_bcc = opt[:bcc] == true
|
111
|
+
|
112
|
+
# The version of the email we will be sending.
|
113
|
+
_view = email.view(:preview, :email, version)
|
114
|
+
|
115
|
+
_subject = _view.subject
|
116
|
+
_subject = "#{_subject} (#{_tag})" unless _tag.blank?
|
117
|
+
|
118
|
+
mail = Mail.new do
|
119
|
+
|
120
|
+
to opt[:to] || _from
|
121
|
+
cc opt[:cc]
|
122
|
+
from _from
|
123
|
+
subject _subject
|
124
|
+
|
125
|
+
bcc(_from) if _bcc
|
126
|
+
|
127
|
+
html_part do
|
128
|
+
content_type 'text/html; charset=UTF-8'
|
129
|
+
body _view.render!
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
|
134
|
+
mail.deliver!
|
135
|
+
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
@@ -0,0 +1,151 @@
|
|
1
|
+
require 'yui/compressor'
|
2
|
+
|
3
|
+
module Inkcite
|
4
|
+
class Minifier
|
5
|
+
|
6
|
+
# Directory of optimized images
|
7
|
+
IMAGE_CACHE = ".images"
|
8
|
+
|
9
|
+
def self.css code, ctx
|
10
|
+
minify?(ctx) ? css_compressor(ctx).compress(code) : code
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.html lines, ctx
|
14
|
+
|
15
|
+
if minify?(ctx)
|
16
|
+
|
17
|
+
# Will hold the assembled, minified HTML as it is prepared.
|
18
|
+
html = ''
|
19
|
+
|
20
|
+
# Will hold the line being assembled until it reaches the maximum
|
21
|
+
# allowed line length.
|
22
|
+
packed_line = ''
|
23
|
+
|
24
|
+
lines.each do |line|
|
25
|
+
next if line.blank?
|
26
|
+
|
27
|
+
line.strip!
|
28
|
+
|
29
|
+
## Compress all in-line styles.
|
30
|
+
#Parser.each line, INLINE_STYLE_REGEX do |style|
|
31
|
+
# style.gsub!(/: +/, ':')
|
32
|
+
# style.gsub!(/; +/, ';')
|
33
|
+
# style.gsub!(/;+/, ';')
|
34
|
+
# style.gsub!(/;+$/, '')
|
35
|
+
# "style=\"#{style}\""
|
36
|
+
#end
|
37
|
+
|
38
|
+
# If the length of the packed line with the addition of this line of content would
|
39
|
+
# exceed the maximum allowed line length, then push the collected lines onto the
|
40
|
+
# html and start a new line.
|
41
|
+
if !packed_line.blank? && packed_line.length + line.length > MAXIMUM_LINE_LENGTH
|
42
|
+
html << packed_line
|
43
|
+
html << NEW_LINE
|
44
|
+
packed_line = ''
|
45
|
+
end
|
46
|
+
|
47
|
+
packed_line << line
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
# Make sure to get any last lines assembled on the packed line.
|
52
|
+
html << packed_line unless packed_line.blank?
|
53
|
+
|
54
|
+
html
|
55
|
+
else
|
56
|
+
lines.join(NEW_LINE)
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.images email
|
63
|
+
|
64
|
+
image_optim_path = '/Applications/ImageOptim.app/Contents/MacOS/ImageOptim'
|
65
|
+
image_optim = File.exists?(image_optim_path)
|
66
|
+
abort "Can't find ImageOptim (#{image_optim_path}) - download it from https://imageoptim.com" unless image_optim
|
67
|
+
|
68
|
+
images_path = email.image_dir
|
69
|
+
cache_path = email.project_file(IMAGE_CACHE)
|
70
|
+
|
71
|
+
# If the image cache exists, we need to check to see if any images have been
|
72
|
+
# removed since the last build.
|
73
|
+
if File.exists?(cache_path)
|
74
|
+
|
75
|
+
# Get a list of the files in the cache that do not also exist in the
|
76
|
+
# project's images/ directory.
|
77
|
+
removed_images = Dir.entries(cache_path) - Dir.entries(images_path)
|
78
|
+
unless removed_images.blank?
|
79
|
+
|
80
|
+
# Convert the images to fully-qualified paths and then remove
|
81
|
+
# those files from the cache
|
82
|
+
removed_images = removed_images.collect { |img| File.join(cache_path, img ) }
|
83
|
+
FileUtils.rm (removed_images)
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
# Check to see if there are new or updated images that need to be re-optimized.
|
90
|
+
updated_images = Dir.entries(images_path).select do |img|
|
91
|
+
unless img.start_with?('.')
|
92
|
+
cimg = File.join(cache_path, img)
|
93
|
+
!File.exists?(cimg) || (File.stat(File.join(images_path, img)).mtime > File.stat(cimg).mtime)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
return if updated_images.blank?
|
98
|
+
|
99
|
+
# This is the temporary path into which new or updated images will
|
100
|
+
# be copied and then optimized.
|
101
|
+
temp_path = email.project_file(IMAGE_TEMP)
|
102
|
+
|
103
|
+
# Make sure there is no existing temporary directory to interfere
|
104
|
+
# with the image processing.
|
105
|
+
FileUtils.rm_rf(temp_path)
|
106
|
+
FileUtils.mkpath(temp_path)
|
107
|
+
|
108
|
+
# Copy all of the images that need updating into the temporary directory.
|
109
|
+
# Specifically joining the images_path to the image to avoid Email's
|
110
|
+
# image_path which may change it's directory if optimization is enabled.
|
111
|
+
updated_images.each { |img| FileUtils.cp(File.join(images_path, img), File.join(temp_path, img)) }
|
112
|
+
|
113
|
+
# Optimize all of the images.
|
114
|
+
system("#{image_optim_path} #{temp_path}") if image_optim
|
115
|
+
|
116
|
+
FileUtils.cp_r(File.join(temp_path, "."), cache_path)
|
117
|
+
FileUtils.rm_rf(temp_path)
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
def self.js code, ctx
|
122
|
+
minify?(ctx) ? js_compressor(ctx).compress(code) : code
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
|
127
|
+
# Temporary directory that new or updated images will be copied into
|
128
|
+
# to be optimized and then cached in .images
|
129
|
+
IMAGE_TEMP = ".images-temp"
|
130
|
+
|
131
|
+
NEW_LINE = "\n"
|
132
|
+
MAXIMUM_LINE_LENGTH = 800
|
133
|
+
|
134
|
+
# Used to match inline styles that will be compressed when minifying
|
135
|
+
# the entire email.
|
136
|
+
INLINE_STYLE_REGEX = /style=\"([^\"]+)\"/
|
137
|
+
|
138
|
+
def self.minify? ctx
|
139
|
+
ctx.is_enabled?(:minify)
|
140
|
+
end
|
141
|
+
|
142
|
+
def self.js_compressor ctx
|
143
|
+
ctx.js_compressor ||= YUI::JavaScriptCompressor.new(:munge => true)
|
144
|
+
end
|
145
|
+
|
146
|
+
def self.css_compressor ctx
|
147
|
+
ctx.css_compressor ||= YUI::CssCompressor.new(:line_break => (ctx.email?? MAXIMUM_LINE_LENGTH : nil))
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
151
|
+
end
|