inkcite 1.14.0 → 1.15.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/assets/init/config.yml +2 -0
  3. data/assets/social/facebook.png +0 -0
  4. data/assets/social/instagram.png +0 -0
  5. data/assets/social/pintrest.png +0 -0
  6. data/assets/social/twitter.png +0 -0
  7. data/inkcite.gemspec +2 -2
  8. data/lib/inkcite.rb +1 -0
  9. data/lib/inkcite/cli/base.rb +32 -6
  10. data/lib/inkcite/cli/preview.rb +24 -28
  11. data/lib/inkcite/cli/server.rb +22 -19
  12. data/lib/inkcite/cli/test.rb +1 -1
  13. data/lib/inkcite/email.rb +8 -4
  14. data/lib/inkcite/facade/animation.rb +4 -0
  15. data/lib/inkcite/facade/keyframe.rb +26 -3
  16. data/lib/inkcite/image/base.rb +38 -0
  17. data/lib/inkcite/image/guetzli_minifier.rb +62 -0
  18. data/lib/inkcite/image/image_minifier.rb +143 -0
  19. data/lib/inkcite/image/image_optim_minifier.rb +90 -0
  20. data/lib/inkcite/image/mozjpeg_minifier.rb +92 -0
  21. data/lib/inkcite/mailer.rb +201 -112
  22. data/lib/inkcite/minifier.rb +2 -146
  23. data/lib/inkcite/post_processor.rb +13 -0
  24. data/lib/inkcite/renderer.rb +19 -0
  25. data/lib/inkcite/renderer/background.rb +53 -14
  26. data/lib/inkcite/renderer/base.rb +29 -15
  27. data/lib/inkcite/renderer/button.rb +1 -1
  28. data/lib/inkcite/renderer/carousel.rb +245 -0
  29. data/lib/inkcite/renderer/container_base.rb +10 -0
  30. data/lib/inkcite/renderer/div.rb +1 -3
  31. data/lib/inkcite/renderer/fireworks.rb +54 -40
  32. data/lib/inkcite/renderer/footnote.rb +22 -2
  33. data/lib/inkcite/renderer/image.rb +11 -0
  34. data/lib/inkcite/renderer/image_base.rb +3 -6
  35. data/lib/inkcite/renderer/in_browser.rb +4 -0
  36. data/lib/inkcite/renderer/link.rb +39 -12
  37. data/lib/inkcite/renderer/mobile_image.rb +1 -1
  38. data/lib/inkcite/renderer/responsive.rb +9 -1
  39. data/lib/inkcite/renderer/social.rb +31 -3
  40. data/lib/inkcite/renderer/special_effect.rb +22 -13
  41. data/lib/inkcite/renderer/sup.rb +32 -0
  42. data/lib/inkcite/renderer/table_base.rb +3 -0
  43. data/lib/inkcite/renderer/topic.rb +76 -0
  44. data/lib/inkcite/renderer/trademark.rb +47 -0
  45. data/lib/inkcite/renderer/video_preview.rb +3 -2
  46. data/lib/inkcite/uploader.rb +2 -3
  47. data/lib/inkcite/util.rb +51 -0
  48. data/lib/inkcite/version.rb +1 -1
  49. data/lib/inkcite/view.rb +140 -54
  50. data/lib/inkcite/view/context.rb +1 -31
  51. data/lib/inkcite/view/media_query.rb +6 -0
  52. data/test/animation_spec.rb +7 -0
  53. data/test/parser_spec.rb +1 -1
  54. data/test/renderer/background_spec.rb +16 -12
  55. data/test/renderer/div_spec.rb +11 -0
  56. data/test/renderer/footnote_spec.rb +5 -1
  57. data/test/renderer/image_spec.rb +51 -28
  58. data/test/renderer/link_spec.rb +20 -8
  59. data/test/renderer/lorem_spec.rb +2 -2
  60. data/test/renderer/mobile_image_spec.rb +6 -0
  61. data/test/renderer/mobile_style_spec.rb +3 -3
  62. data/test/renderer/redacted_spec.rb +2 -2
  63. data/test/renderer/social_spec.rb +6 -6
  64. data/test/renderer/table_spec.rb +4 -0
  65. data/test/renderer/topic_spec.rb +28 -0
  66. data/test/renderer/trademark_spec.rb +40 -0
  67. data/test/renderer/video_preview_spec.rb +1 -1
  68. data/test/test_helper.rb +14 -0
  69. data/test/view_spec.rb +4 -0
  70. metadata +26 -12
  71. data/assets/init/image_optim.yml +0 -37
@@ -0,0 +1,143 @@
1
+ require_relative 'base'
2
+ require_relative 'guetzli_minifier'
3
+ require_relative 'image_optim_minifier'
4
+ require_relative 'mozjpeg_minifier'
5
+
6
+ module Inkcite
7
+ module Image
8
+ class ImageMinifier
9
+
10
+ # Directory of optimized images
11
+ IMAGE_CACHE = 'images-optim'
12
+
13
+ # Common extensions
14
+ GIF = 'gif'
15
+ JPG = 'jpg'
16
+ PNG = 'png'
17
+
18
+ def self.minify email, img_name, force=false
19
+
20
+ # Original, unoptimized source image
21
+ source_img = File.join(email.image_dir, img_name)
22
+
23
+ # Cached, optimized path for this image.
24
+ cache_path = email.project_file(IMAGE_CACHE)
25
+ cached_img = File.join(cache_path, File.basename(img_name))
26
+
27
+ # Grab the first file that exists for this project.
28
+ config_path = email.config_file
29
+
30
+ unless force
31
+
32
+ # Get the last-modified date of the image optimization config
33
+ # file - if that file is newer than the image, re-optimization
34
+ # is necessary because the settings have changed.
35
+ config_last_modified = Util.last_modified(config_path)
36
+
37
+ # Get the last-modified date of the actual image. If the source
38
+ # image is newer than the cached version, we'll need to run it
39
+ # through optimization again, too.
40
+ cache_last_modified = Util.last_modified(cached_img)
41
+ source_last_modified = Util.last_modified(source_img)
42
+
43
+ # Nothing to do unless the image in the cache is older than the
44
+ # source or the config file.
45
+ return unless config_last_modified > cache_last_modified || source_last_modified > cache_last_modified
46
+
47
+ end
48
+
49
+ # Make sure the image cache directory exists
50
+ FileUtils.mkpath(cache_path)
51
+
52
+ # Copy the original image to the cache where it can be processed.
53
+ FileUtils.copy(source_img, cached_img)
54
+
55
+ # Get the file format (e.g. gif) of the file being optimized.
56
+ source_ext = Util::file_extension(source_img)
57
+
58
+ # This will hold the list of minifiers that will be applied to the
59
+ # image, based on its extension.
60
+ pipeline = []
61
+
62
+ # True if ImageOptim is allowed to make lossy optimizations to the
63
+ # images. When false, even if the quality settings allow it, ImageOptim
64
+ # won't make lossy optimizations.
65
+ allow_lossy = true
66
+
67
+ if source_ext == JPG
68
+ pipeline << MozjpegMinifier.new
69
+ pipeline << GuetzliMinifier.new
70
+ allow_lossy = false
71
+ end
72
+
73
+ # Always optimize with ImageOptim although for JPGs additional
74
+ # lossy compression is force disabled.
75
+ pipeline << ImageOptimMinifier.new(allow_lossy)
76
+
77
+ original_size = File.size(source_img)
78
+
79
+ msg = "Compressing #{img_name} #{Util.pretty_file_size(original_size)}"
80
+
81
+ # Process the image
82
+ pipeline.each do |p|
83
+
84
+ # Minifiers don't work well when the source and destination images are the
85
+ # same files - so move the image to a temporary file so the minifier can
86
+ # optimize it back into place.
87
+ temp_img = "#{cached_img}.tmp"
88
+ FileUtils.move(cached_img, temp_img)
89
+
90
+ temp_size = File.size(temp_img)
91
+
92
+ # Let the processor compress the image
93
+ p.minify!(email, temp_img, cached_img)
94
+
95
+ compressed_size = File.size(cached_img)
96
+ if compressed_size < temp_size
97
+ msg << " >> #{p.name} #{Util.pretty_file_size(compressed_size)}"
98
+
99
+ else
100
+
101
+ # Occassionally the compressor does the wrong thing and
102
+ # makes the image bigger (particularly ImageOptim after
103
+ # Guetzli) so in that case, revert the image to its
104
+ # smaller pre-optimization form.
105
+ FileUtils.copy(temp_img, cached_img)
106
+
107
+ end
108
+
109
+ # Now remove the temp file.
110
+ FileUtils.remove(temp_img) if File.exists?(temp_img)
111
+
112
+ end
113
+
114
+ # Get the final compressed size of the image so we can print the
115
+ # resulting compression ratio.
116
+ compressed_size = File.size(cached_img)
117
+ msg << " (#{self.compressed_percent(original_size, compressed_size)}%)"
118
+
119
+ Util.log msg
120
+
121
+ end
122
+
123
+ # Minifies all of the images in the provided email's project directory.
124
+ def self.minify_all email, force=false
125
+
126
+ images_path = File.join(email.image_dir, '*.*')
127
+
128
+ # Iterate through all of the images in the project and optimize them
129
+ # if necessary.
130
+ Dir.glob(images_path).each do |img|
131
+ self.minify(email, File.basename(img), force)
132
+ end
133
+
134
+ end
135
+
136
+ def self.compressed_percent original_size, compressed_size
137
+ ((1.0 - (compressed_size / original_size.to_f)) * 100).round(1)
138
+ end
139
+
140
+ end
141
+ end
142
+ end
143
+
@@ -0,0 +1,90 @@
1
+ module Inkcite
2
+ module Image
3
+ class ImageOptimMinifier < ImageMinifier::Base
4
+
5
+ def initialize allow_lossy=true
6
+ super('ImageOptim')
7
+ @allow_lossy = allow_lossy
8
+ end
9
+
10
+ def minify! email, source_img, cache_img
11
+
12
+ config = email.config
13
+
14
+ img_type = Util::file_extension(cache_img)
15
+
16
+ # This will hold the settings that control how imageoptim
17
+ # compresses the image, based on extension.
18
+ optim_opt = {
19
+ :verbose => false,
20
+ :svgo => false
21
+ }
22
+
23
+ FileUtils.copy(source_img, cache_img)
24
+
25
+ case img_type
26
+ when ImageMinifier::GIF
27
+ mix_gif_options email, config, optim_opt
28
+ when ImageMinifier::JPG
29
+ mix_jpg_options email, config, optim_opt
30
+ when ImageMinifier::PNG
31
+ mix_png_options email, config, optim_opt
32
+ else
33
+ # Don't know how to compress this type of image so
34
+ # just leave it alone
35
+ return
36
+ end
37
+
38
+ ImageOptim.new(optim_opt).optimize_image!(cache_img)
39
+
40
+ end
41
+
42
+ private
43
+
44
+ # Name of the configuration field that controls ImageOptim's JPG quality
45
+ IMAGEOPTIM_JPG_QUALITY = :'imageopt-jpg-quality'
46
+
47
+ # Default JPG image quality
48
+ DEFAULT_JPG_QUALITY = 85
49
+
50
+ # Quality constraints
51
+ MIN_QUALITY = 0
52
+ MAX_QUALITY = 100
53
+
54
+ def mix_gif_options email, config, opts
55
+
56
+ end
57
+
58
+ def mix_jpg_options email, config, opts
59
+
60
+ max_quality = get_jpg_quality(config, IMAGEOPTIM_JPG_QUALITY, DEFAULT_JPG_QUALITY)
61
+
62
+ # Anything less than 100% quality means lossy is enabled.
63
+ lossy = @allow_lossy && max_quality < 100
64
+ opts[:allow_lossy] = lossy
65
+
66
+ # Additional configuration necessary only if lossy compression
67
+ # is enabled. Otherwise, the defaults are acceptable.
68
+ if lossy
69
+ opts[:jpegoptim] = {
70
+ :allow_lossy => lossy,
71
+ :max_quality => max_quality
72
+ }
73
+
74
+ opts[:jpegrecompress] = {
75
+ :allow_lossy => lossy,
76
+ :quality => (max_quality / 100.0 * 3).round(0)
77
+ }
78
+ end
79
+
80
+
81
+ end
82
+
83
+ def mix_png_options email, config, opts
84
+
85
+ end
86
+
87
+
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,92 @@
1
+ require 'mozjpeg'
2
+
3
+ module Inkcite
4
+ module Image
5
+ class MozjpegMinifier < ImageMinifier::Base
6
+
7
+ def initialize
8
+ super('Mozjpeg')
9
+ end
10
+
11
+ def minify! email, source_img, cache_img
12
+
13
+ config = email.config
14
+
15
+ cmd = []
16
+
17
+ # mozjpeg usage documentation available
18
+ # https://github.com/mozilla/mozjpeg/blob/master/usage.txt
19
+ cmd << Mozjpeg.cjpeg_path
20
+
21
+ chrominance_quality = get_jpg_quality(config, MOZJPEG_CHROMINANCE_QUALITY, DEFAULT_QUALITY)
22
+
23
+ # The human eye is more sensitive to spatial changes in brightness than
24
+ # spatial changes in color, the chrominance components can be quantized more
25
+ # than the luminance components without incurring any visible image quality loss.
26
+ luminance_quality = (config[MOZJPEG_LUMINANCE_QUALITY] || DEFAULT_QUALITY).to_i # Recommended default
27
+ luminance_quality = MAX_QUALITY if luminance_quality > MAX_QUALITY
28
+
29
+ cmd << "-quality #{luminance_quality},#{chrominance_quality}"
30
+
31
+ # Perform optimization of entropy encoding parameters.
32
+ # -optimize usually makes the JPEG file a little smaller,
33
+ # but cjpeg runs somewhat slower and needs much more
34
+ # memory. Image quality and speed of decompression are
35
+ # unaffected by -optimize.
36
+ cmd << '-optimize'
37
+
38
+ # Produce progressive JPEGs
39
+ cmd << '-progressive'
40
+
41
+ subsampling = (config[MOZJPEG_SUBSAMPLING] || DEFAULT_SUBSAMPLING).to_i
42
+ cmd << "-sample #{subsampling}x#{subsampling}"
43
+
44
+ # Reduces posterization in lower-quality JPEGs
45
+ # https://calendar.perfplanet.com/2014/mozjpeg-3-0/
46
+ quant_table = (config[MOZJPEG_QUANT_TABLE] || MS_SSIM).to_i
47
+ cmd << "-quant-table #{quant_table}"
48
+
49
+ # Makes images sharper, at the cost of increased file size
50
+ # https://calendar.perfplanet.com/2014/mozjpeg-3-0/
51
+ cmd << '-notrellis'
52
+
53
+ cmd << %Q(-outfile "#{cache_img}")
54
+ cmd << %Q("#{source_img}")
55
+
56
+ Util::exec(cmd.join(' '))
57
+
58
+ true
59
+ end
60
+
61
+ private
62
+
63
+ # Name of the configuration attributes used to control
64
+ # JPEG compression.
65
+ MOZJPEG_LUMINANCE_QUALITY = :'mozjpeg-luminance-quality'
66
+ MOZJPEG_CHROMINANCE_QUALITY = :'mozjpeg-chrominance-quality'
67
+ MOZJPEG_QUANT_TABLE = :'mozjpeg-quant-table'
68
+ MOZJPEG_SUBSAMPLING = :'mozjpeg-subsampling'
69
+
70
+ # Default compression quality for images unless specified in the
71
+ # configuration file under 'mozjpeg-quality'
72
+ DEFAULT_QUALITY = 85
73
+
74
+ # Default subsampling
75
+ DEFAULT_SUBSAMPLING = 1
76
+
77
+ # Quality limits
78
+ MIN_QUALITY = 0
79
+ MAX_QUALITY = 100
80
+
81
+ # Quant table constants per the mozjpeg man page
82
+ JPEG_ANNEX_K = 0
83
+ FLAT = 1
84
+ MS_SSIM = 2
85
+ IMAGEMAGICK = 3
86
+ PSNR_HVS = 4
87
+ KLEIN_SILVERSTEIN_CARNEY = 5
88
+
89
+ end
90
+
91
+ end
92
+ end
@@ -4,166 +4,258 @@ require 'mailgun'
4
4
  module Inkcite
5
5
  class Mailer
6
6
 
7
- def self.client email, opts
7
+ def self.send_test email, test_address, opts
8
8
 
9
- # Determine which preview this is
10
- count = increment(email, :preview)
9
+ # Determine the number of times we've emailed to this list
10
+ count = get_count(email, :test, opts)
11
11
 
12
- # Get the declared set of recipients.
13
- recipients = email.config[:recipients]
12
+ # Check to see if a specific version is requested or if unspecified
13
+ # all versions of the email should be sent.
14
+ versions = get_versions(email, opts)
14
15
 
15
- # Get the list of client address(es) - check both client and clients
16
- # as a convenience.
17
- to = recipients[:clients] || recipients[:client]
16
+ # Will hold the instance of the Mailer::Base that will handle the
17
+ # actual sending of the email.
18
+ mailer_base = get_mailer_base(email)
18
19
 
19
- # If this is the first preview, check to see if there is an
20
- # additional set of recipients for this initial mailing.
21
- if count == 1
22
- also_to = recipients[FIRST_PREVIEW]
23
- #to = [* to] + [* also_to] unless also_to.blank?
24
- to = [* also_to] unless also_to.blank?
25
- end
20
+ versions.each do |version|
26
21
 
27
- # Always cc internal recipients so everyone stays informed of feedback.
28
- cc = recipients[:internal]
22
+ # The version of the email we will be sending.
23
+ view = email.view(:preview, :email, version)
29
24
 
30
- self.send(email, opts.merge({
31
- :to => to,
32
- :cc => cc,
33
- :bcc => true,
34
- :tag => "Preview ##{count}"
35
- }))
25
+ subject = "#{view.subject} (Test ##{count})"
26
+ print "Sending '#{subject}' ... "
36
27
 
37
- end
28
+ mailer_base.send!({ :to => test_address }, subject, view.render!)
38
29
 
39
- def self.developer email, opts
30
+ puts 'Sent!'
40
31
 
41
- count = increment(email, :developer)
32
+ end
42
33
 
43
- self.send(email, opts.merge({
44
- :tag => "Developer Test ##{count}"
45
- }))
34
+ # Increment the count now that we've successfully emailed this list
35
+ increment email, :test
46
36
 
47
37
  end
48
38
 
49
- def self.internal email, opts
39
+ def self.send_to_list email, list, opts
40
+
41
+ # Determine the number of times we've emailed to this list
42
+ count = get_count(email, list, opts)
43
+
44
+ # Check to see if a specific version is requested or if unspecified
45
+ # all versions of the email should be sent.
46
+ versions = get_versions(email, opts)
47
+
48
+ # Will hold the instance of the Mailer::Base that will handle the
49
+ # actual sending of the email.
50
+ mailer_base = get_mailer_base(email)
51
+
52
+ # Get the email address from which the previews will be sent.
53
+ from = mailer_base.get_from_address
54
+
55
+ versions.each do |version|
56
+
57
+ # The version of the email we will be sending.
58
+ view = email.view(:preview, :email, version)
59
+
60
+ # Subject line tag such as "Preview #3"
61
+ tag = "#{opts[:tag]} ##{count}"
62
+
63
+ subject = view.subject
64
+ subject = "#{subject} (#{tag})" unless tag.blank?
65
+
66
+ # Get the list of recipients for this version
67
+ recipients = get_recipients(list, view, count, from)
50
68
 
51
- recipients = email.config[:recipients]
69
+ # Get the total number of recipients for this version
70
+ total_recipients = recipients.inject(0) { |total, (k, v)| total + v.length }
52
71
 
53
- # Determine which preview this is
54
- count = increment(email, :internal)
72
+ print "Sending '#{subject}' to #{total_recipients} recipient#{'s' if total_recipients > 1} ... "
55
73
 
56
- self.send(email, opts.merge({
57
- :to => recipients[:internal],
58
- :bcc => true,
59
- :tag => "Internal Proof ##{count}"
60
- }))
74
+ mailer_base.send! recipients, subject, view.render!
75
+
76
+ puts 'Sent!'
77
+
78
+ end
79
+
80
+ # Increment the count now that we've successfully emailed this list
81
+ increment email, list
61
82
 
62
83
  end
63
84
 
64
- # Sends each version of the provided email with the indicated options.
65
- def self.send email, opts
85
+ private
66
86
 
67
- # Check to see if additional one-time recipients have been added
68
- # to the command line.
69
- also_to = opts[:also]
70
- opts[:cc] = [*opts[:cc]] + also_to unless also_to.blank?
87
+ # Name of the distribution list used on the first preview. For one
88
+ # client, they wanted the first preview sent to additional people
89
+ # but subsequent previews went to a shorter list.
90
+ FIRST_PREVIEW = :'first-preview'
71
91
 
72
- # Check to see if a specific version is requested or if unspecified
73
- # all versions of the email should be sent.
74
- versions = Array(opts[:version] || email.versions)
92
+ def self.comma_set_includes? _set, value
93
+ _set.blank? || _set.split(',').collect(&:to_sym).include?(value.to_sym)
94
+ end
75
95
 
76
- # Will hold the instance of the Mailer::Base that will handle the
77
- # actual sending of the email.
96
+ def self.get_count email, sym, opts
97
+ opts[:count] || email.meta(sym).to_i + 1
98
+ end
99
+
100
+ def self.get_mailer_base email
78
101
  mailer_base = nil
79
102
 
80
103
  # Check to see if
81
104
  if config = email.config[:mailgun]
82
- mailer_base = MailgunMailer.new
105
+ mailer_base = MailgunMailer.new(config)
83
106
  elsif config = email.config[:smtp]
84
- mailer_base = SmtpMailer.new
107
+ mailer_base = SmtpMailer.new(config)
85
108
  else
86
109
  abort <<-USAGE.strip_heredoc
87
110
 
88
- Oops! Inkcite can't send this email because of a configuration problem.
89
- Please update the mailgun or smtp sections of your config.yml file.
111
+ Oops! Inkcite can't send this email because of a configuration problem.
112
+ Please update the mailgun or smtp sections of your config.yml file.
90
113
 
91
- smtp:
92
- host: 'smtp.gmail.com'
93
- port: 587
94
- domain: 'yourdomain.com'
95
- username: ''
96
- password: ''
97
- from: 'Your Name <email@domain.com>'
114
+ smtp:
115
+ host: 'smtp.gmail.com'
116
+ port: 587
117
+ domain: 'yourdomain.com'
118
+ username: ''
119
+ password: ''
120
+ from: 'Your Name <email@domain.com>'
98
121
 
99
- Or send via Mailgun:
122
+ Or send via Mailgun:
100
123
 
101
- mailgun:
102
- api-key: 'key-your-api-key'
103
- domain: 'mg.sending-domain.com'
104
- from: 'Your Name <email@domain.com>'
124
+ mailgun:
125
+ api-key: 'key-your-api-key'
126
+ domain: 'mg.sending-domain.com'
127
+ from: 'Your Name <email@domain.com>'
105
128
 
106
129
  USAGE
107
130
  end
108
131
 
109
- versions.each do |version|
132
+ mailer_base
133
+ end
110
134
 
111
- # The version of the email we will be sending.
112
- view = email.view(:preview, :email, version)
135
+ def self.get_recipients list, view, count, from
113
136
 
114
- # Subject line tag such as "Preview #3"
115
- tag = opts[:tag]
137
+ recipients = { :to => [], :cc => [], :bcc => [] }
116
138
 
117
- subject = view.subject
118
- subject = "#{subject} (#{tag})" unless tag.blank?
139
+ # Developer list always only sends to the original from address.
140
+ if list == :developer
141
+ recipients[:to] << from
142
+
143
+ else
144
+
145
+ # Always bcc the developer of the email
146
+ recipients[:bcc] << from
147
+
148
+ # Check to see if there is a TSV file which allows for maximum
149
+ # configurability of the recipient list.
150
+ recipients_file = view.email.project_file('recipients.tsv')
151
+ if File.exist?(recipients_file)
152
+
153
+ # Iterate through the recipients file and determine which entries match the
154
+ # list, version and preview count...
155
+ Util.each_line(recipients_file, false) do |line|
156
+
157
+ # Skip comments
158
+ next if line.start_with?('#')
119
159
 
120
- puts "Sending '#{subject}' ..."
160
+ name, email, _list, delivery, min_preview, max_preview, versions = line.split("\t")
161
+ next if name.blank? || email.blank?
121
162
 
122
- mailer_base.send! config, view, subject, opts
163
+ # Skip this recipient unless the distribution list matches the one
164
+ # we're looking for.
165
+ next unless _list.to_sym == list
166
+
167
+ # Skip this recipient unless we've reached the minimum number of
168
+ # earlier previews for this recipient - e.g. they only receive the
169
+ # 2nd previews and beyond
170
+ min_preview = min_preview.to_i
171
+ next if min_preview > 0 && count < min_preview
172
+
173
+ # Skip this recipient if we've already delivered the maximum number
174
+ # of previews they should receive - e.g. they only receive the
175
+ # first preview, no additional previews.
176
+ max_preview = max_preview.to_i
177
+ next if max_preview > 0 && count > max_preview
178
+
179
+ # Skip this recipient unless
180
+ next unless comma_set_includes?(versions, view.version)
181
+
182
+ delivery = delivery.blank? ? :to : delivery.to_sym
183
+ recipient = "#{name} <#{email}>"
184
+ recipients[delivery] << recipient
185
+
186
+ end
187
+
188
+ else
189
+
190
+ # Grab the array of recipients from the config.yml
191
+ recipient_yml = view[:recipients]
192
+
193
+ case list
194
+ when :client
195
+ first_preview = count == 1 ? recipient_yml[FIRST_PREVIEW] : nil
196
+ clients = recipient_yml[:clients] || recipient_yml[:client]
197
+ recipients[:to] = first_preview || clients
198
+ recipients[:cc] = recipient_yml[:internal]
199
+ when :internal
200
+ recipients[:to] = recipient_yml[:internal]
201
+ when :developer
202
+ recipients[:to] = from
203
+ end
204
+
205
+ end
123
206
 
124
207
  end
125
208
 
209
+ return recipients
126
210
  end
127
211
 
128
- private
129
-
130
- # Name of the distribution list used on the first preview. For one
131
- # client, they wanted the first preview sent to additional people
132
- # but subsequent previews went to a shorter list.
133
- FIRST_PREVIEW = :'first-preview'
212
+ def self.get_versions email, opts
213
+ Array(opts[:version] || email.versions)
214
+ end
134
215
 
135
216
  def self.increment email, sym
136
- count = email.meta(sym).to_i + 1
137
- email.set_meta sym, count
217
+ email.set_meta sym, get_count(email, sym, {})
138
218
  end
139
219
 
140
220
  # Abstract base class for the workhorses of the Mailer class.
141
221
  # Instantiated based on the config.yml settings.
142
222
  class Base
143
- def send! config, view, subject, opt
223
+
224
+ attr_accessor :config
225
+
226
+ def initialize config
227
+ @config = config
228
+ end
229
+
230
+ def get_from_address
231
+ @config[:from]
232
+ end
233
+
234
+ def send! recipients, subject, content
144
235
  raise NotImplementedError
145
236
  end
146
237
  end
147
238
 
148
239
  class MailgunMailer < Base
149
- def send! config, view, subject, opt
240
+ def initialize config
241
+ super(config)
242
+ end
150
243
 
151
- # The address of the developer
152
- from = config[:from]
244
+ def send! recipients, subject, content
153
245
 
154
246
  # First, instantiate the Mailgun Client with your API key
155
247
  mg_client = Mailgun::Client.new config[:'api-key']
156
248
 
157
249
  # Define your message parameters
158
250
  message_params = {
159
- :from => from,
160
- :to => opt[:to] || from,
251
+ :from => get_from_address,
252
+ :to => recipients[:to],
161
253
  :subject => subject,
162
- :html => view.render!
254
+ :html => content
163
255
  }
164
256
 
165
- message_params[:cc] = opt[:cc] unless opt[:cc].blank?
166
- message_params[:bcc] = from if opt[:bcc] == true
257
+ message_params[:cc] = recipients[:cc] unless recipients[:cc].blank?
258
+ message_params[:bcc] = recipients[:bcc] unless recipients[:bcc].blank?
167
259
 
168
260
  # Send your message through the client
169
261
  mg_client.send_message config[:domain], message_params
@@ -172,41 +264,38 @@ module Inkcite
172
264
  end
173
265
 
174
266
  class SmtpMailer < Base
175
- def send! config, view, _subject, opt
267
+ def initialize config
268
+ super(config)
269
+ end
270
+
271
+ def send! recipients, subject, content
272
+
273
+ _config = config
176
274
 
177
275
  Mail.defaults do
178
276
  delivery_method :smtp, {
179
- :address => config[:host],
180
- :port => config[:port],
181
- :user_name => config[:username],
182
- :password => config[:password],
277
+ :address => _config[:host],
278
+ :port => _config[:port],
279
+ :user_name => _config[:username],
280
+ :password => _config[:password],
183
281
  :authentication => :plain,
184
282
  :enable_starttls_auto => true
185
283
  }
186
284
  end
187
285
 
188
- # The address of the developer
189
- _from = config[:from]
190
-
191
- # True if the developer should be bcc'd.
192
- _bcc = !!opt[:bcc]
193
-
194
286
  mail = Mail.new do
195
-
196
- to opt[:to] || _from
197
- cc opt[:cc]
198
- from _from
199
- subject _subject
200
-
201
- bcc(_from) if _bcc
202
-
203
287
  html_part do
204
288
  content_type 'text/html; charset=UTF-8'
205
- body view.render!
289
+ body content
206
290
  end
207
-
208
291
  end
209
292
 
293
+ mail[:to] = recipients[:to]
294
+ mail[:cc] = recipients[:cc]
295
+ mail[:bcc] = recipients[:bcc]
296
+ mail[:from] = get_from_address
297
+ mail[:subject] = subject
298
+
210
299
  mail.deliver!
211
300
 
212
301
  end