inkcite 1.15.0 → 1.16.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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +1 -1
  3. data/inkcite.gemspec +0 -3
  4. data/lib/inkcite.rb +0 -1
  5. data/lib/inkcite/cli/base.rb +12 -8
  6. data/lib/inkcite/cli/preview.rb +0 -10
  7. data/lib/inkcite/cli/server.rb +9 -1
  8. data/lib/inkcite/email.rb +5 -10
  9. data/lib/inkcite/facade/animation.rb +4 -1
  10. data/lib/inkcite/image_minifier.rb +96 -0
  11. data/lib/inkcite/mailer.rb +2 -2
  12. data/lib/inkcite/minifier.rb +1 -1
  13. data/lib/inkcite/renderer.rb +10 -0
  14. data/lib/inkcite/renderer/base.rb +47 -2
  15. data/lib/inkcite/renderer/button.rb +15 -5
  16. data/lib/inkcite/renderer/container_base.rb +15 -1
  17. data/lib/inkcite/renderer/image.rb +1 -1
  18. data/lib/inkcite/renderer/image_base.rb +8 -0
  19. data/lib/inkcite/renderer/list.rb +104 -0
  20. data/lib/inkcite/renderer/mobile_image.rb +3 -0
  21. data/lib/inkcite/renderer/responsive.rb +2 -0
  22. data/lib/inkcite/renderer/slant.rb +207 -0
  23. data/lib/inkcite/renderer/snow.rb +15 -1
  24. data/lib/inkcite/renderer/special_effect.rb +7 -0
  25. data/lib/inkcite/renderer/sup.rb +18 -4
  26. data/lib/inkcite/renderer/table.rb +9 -1
  27. data/lib/inkcite/renderer/table_base.rb +20 -2
  28. data/lib/inkcite/renderer/td.rb +7 -2
  29. data/lib/inkcite/renderer/video_preview.rb +1 -1
  30. data/lib/inkcite/uploader.rb +40 -27
  31. data/lib/inkcite/version.rb +1 -1
  32. data/lib/inkcite/view.rb +133 -13
  33. data/lib/inkcite/view/context.rb +6 -1
  34. data/lib/inkcite/view/developer_scripts.rb +56 -0
  35. data/test/renderer/background_spec.rb +4 -4
  36. data/test/renderer/button_spec.rb +14 -8
  37. data/test/renderer/div_spec.rb +26 -3
  38. data/test/renderer/image_spec.rb +9 -4
  39. data/test/renderer/list_spec.rb +36 -0
  40. data/test/renderer/mobile_image_spec.rb +5 -0
  41. data/test/renderer/slant_spec.rb +47 -0
  42. data/test/renderer/span_spec.rb +12 -1
  43. data/test/renderer/table_spec.rb +1 -1
  44. data/test/renderer/td_spec.rb +24 -8
  45. data/test/renderer/trademark_spec.rb +6 -6
  46. metadata +11 -50
  47. data/lib/inkcite/image/base.rb +0 -38
  48. data/lib/inkcite/image/guetzli_minifier.rb +0 -62
  49. data/lib/inkcite/image/image_minifier.rb +0 -143
  50. data/lib/inkcite/image/image_optim_minifier.rb +0 -90
  51. data/lib/inkcite/image/mozjpeg_minifier.rb +0 -92
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6f77f5972f324c7fd1d94e0e62ad167331636c78
4
- data.tar.gz: 86ce04170afb3b33b76aae7b484eac7247de870d
3
+ metadata.gz: 4288c340956699599f226510e4d407f05f9800b9
4
+ data.tar.gz: ec0def8a09ffa81f5e6acaa620d058f3b7c94d85
5
5
  SHA512:
6
- metadata.gz: 78f601288da10bd3db71580b7439c70582a0ec1b9bed2458b9bb79e1e4f6266c8c60cb2c55756fb36809e0ffb003791c150b0a9a010fd375bd4ac9bebd1a5d19
7
- data.tar.gz: 7e817e4e9e5d38df8950ecc8d390bbfe9f3540703b95149e700b90327ce002fcbbe1a492b94baccff4c9faed55a1efe2b8f8ad5e95f044d8d299f1c0cb880fef
6
+ metadata.gz: 7b20720b8716b6ed506383d4e8df220202e00afaf8cc1f48e02d5975952f4c42375cda55b1d4fcd8832d730248f9defd6aca904f47fbdeb17389a228c15cd308
7
+ data.tar.gz: f11d762ebaab6d41e45b6affe121ce9165701a695329fa3220cc817e36c6a40298ad5c6d36114ea11f84684de6bce33e10d2d91c244bde304982f01742994439
data/Rakefile CHANGED
@@ -1,4 +1,4 @@
1
- require "bundler/gem_tasks"
1
+ require 'bundler/gem_tasks'
2
2
  require 'rake/testtask'
3
3
 
4
4
  Rake::TestTask.new do |t|
@@ -31,13 +31,10 @@ Gem::Specification.new do |spec|
31
31
  spec.add_dependency 'guard'
32
32
  spec.add_dependency 'guard-livereload'
33
33
  spec.add_dependency 'htmlbeautifier'
34
- spec.add_dependency 'image_optim'
35
- spec.add_dependency 'image_optim_pack'
36
34
  spec.add_dependency 'listen'
37
35
  spec.add_dependency 'litmus'
38
36
  spec.add_dependency 'mail'
39
37
  spec.add_dependency 'mailgun-ruby'
40
- spec.add_dependency 'mozjpeg'
41
38
  spec.add_dependency 'net-sftp'
42
39
  spec.add_dependency 'rack'
43
40
  spec.add_dependency 'rack-livereload'
@@ -8,7 +8,6 @@ silence_warnings do
8
8
  require 'csv'
9
9
  require 'erubis'
10
10
  require 'i18n'
11
- require 'image_optim'
12
11
  require 'set'
13
12
  require 'uri'
14
13
  require 'yaml'
@@ -41,28 +41,29 @@ module Inkcite
41
41
  Cli::Init.invoke(name, options)
42
42
  end
43
43
 
44
- desc 'minify', 'Minify the images in the project'
44
+ desc 'imageoptim', 'Minify the images in the project using ImageOptim'
45
45
  option :force,
46
46
  :aliases => '-f',
47
- :desc => 'Force re-optimize every image, not just the updated ones',
47
+ :desc => 'Force re-optimize every image, not just ones that were updated since the last minification',
48
48
  :type => :boolean
49
49
  option :image,
50
50
  :aliases => '-i',
51
51
  :desc => 'Optimize a specific image',
52
52
  :type => :string
53
- def minify
53
+ def imageoptim
54
54
 
55
55
  if options[:image]
56
- Image::ImageMinifier.minify(email, options[:image], true)
56
+ ImageMinifier.minify(email, options[:image], true)
57
57
 
58
58
  else
59
- Image::ImageMinifier.minify_all(email, options[:force])
59
+ ImageMinifier.minify_all(email, options[:force])
60
60
 
61
61
  original_size = Util.dir_size(email.image_dir)
62
62
  compressed_size = Util.dir_size(email.optimized_image_dir)
63
- compressed_percent = Image::ImageMinifier.compressed_percent(original_size, compressed_size)
63
+ compressed_percent = ImageMinifier.compressed_percent(original_size, compressed_size)
64
+ image_count = Dir.glob(File.join(email.optimized_image_dir, '*.*')).count
64
65
 
65
- puts "Compressed from #{Util.pretty_file_size(original_size)} to #{Util.pretty_file_size(compressed_size)} (#{compressed_percent}%)"
66
+ puts "Compressed #{image_count} images from #{Util.pretty_file_size(original_size)} to #{Util.pretty_file_size(compressed_size)} (#{compressed_percent}%)"
66
67
  end
67
68
 
68
69
  end
@@ -145,8 +146,11 @@ module Inkcite
145
146
  :aliases => '-f',
146
147
  :desc => "Forces files to be uploaded regardless of whether or not they've changed",
147
148
  :type => :boolean
149
+ option :'images',
150
+ :aliases => '-i',
151
+ :desc => 'Uploads images only (not the email HTML)'
148
152
  def upload
149
- options[:force] ? email.upload! : email.upload
153
+ email.upload options
150
154
  end
151
155
 
152
156
  private
@@ -27,16 +27,6 @@ module Inkcite
27
27
  else
28
28
  preview_opt[:tag] = "#{list.to_s.titleize} Test"
29
29
  preview_opt[:bcc] = true
30
- # abort <<-USAGE.strip_heredoc
31
- #
32
- # Oops! Inkcite doesn't recognize that distribution list. It needs
33
- # to be one of 'client', 'internal' or 'developer':
34
- #
35
- # inkcite preview internal
36
- #
37
- # USAGE
38
- #
39
- # return
40
30
  end
41
31
 
42
32
  Mailer.send_to_list email, list, opt.merge(preview_opt)
@@ -5,6 +5,8 @@ require 'rack'
5
5
  require 'rack-livereload'
6
6
  require 'webrick'
7
7
 
8
+ require 'inkcite/view/developer_scripts'
9
+
8
10
  module Inkcite
9
11
  module Cli
10
12
  class Server
@@ -108,7 +110,7 @@ module Inkcite
108
110
  # Minify the image if the source version in images/ is newer
109
111
  # or if the configuration file controlling optimization has
110
112
  # been updated since the last time the image was requested.
111
- Image::ImageMinifier.minify(@email, File.basename(path), false) if can_serve(path)
113
+ ImageMinifier.minify(@email, File.basename(path), false) if can_serve(path)
112
114
 
113
115
  # Let the super method handle the actual serving of the image.
114
116
  res = super
@@ -158,6 +160,12 @@ module Inkcite
158
160
 
159
161
  view = @email.view(environment, format, version)
160
162
 
163
+ # Check to see if images have been disabled in this rendering.
164
+ view.images_off = true if params.key?('images-off')
165
+
166
+ # Install the developer toolkit into the view.
167
+ Inkcite::View::DeveloperScripts.install!(view)
168
+
161
169
  html = view.render!
162
170
 
163
171
  # If minification is disabled, then beautify the output to make it easier
@@ -61,12 +61,12 @@ module Inkcite
61
61
  # Optimizes this email's images if optimize-images is enabled
62
62
  # in the email configuration.
63
63
  def optimize_images
64
- Image::ImageMinifier.minify_all(self, false) if optimize_images?
64
+ ImageMinifier.minify_all(self, false) if optimize_images?
65
65
  end
66
66
 
67
67
  # Optimizes all of the images in this email.
68
68
  def optimize_images!
69
- Image::ImageMinifier.minify_all(self, true)
69
+ ImageMinifier.minify_all(self, true)
70
70
  end
71
71
 
72
72
  def optimize_images?
@@ -76,7 +76,7 @@ module Inkcite
76
76
  # Returns the directory that optimized, compressed images
77
77
  # have been saved to.
78
78
  def optimized_image_dir
79
- File.join(path, optimize_images?? Image::ImageMinifier::IMAGE_CACHE : IMAGES)
79
+ File.join(path, optimize_images?? ImageMinifier::IMAGE_CACHE : IMAGES)
80
80
  end
81
81
 
82
82
  def project_file file
@@ -90,14 +90,9 @@ module Inkcite
90
90
  value
91
91
  end
92
92
 
93
- def upload
93
+ def upload opts={}
94
94
  require_relative 'uploader'
95
- Uploader.upload(self)
96
- end
97
-
98
- def upload!
99
- require_relative 'uploader'
100
- Uploader.upload!(self)
95
+ Uploader.upload(self, opts)
101
96
  end
102
97
 
103
98
  def versions
@@ -105,7 +105,10 @@ module Inkcite
105
105
  @timing_function
106
106
  ]
107
107
 
108
- css << seconds(@delay) if @delay > 0
108
+ # Negative delays start the animation mid-sequence whereas positive
109
+ # values cause the animation to delay start.
110
+ # https://css-tricks.com/starting-css-animations-mid-way/
111
+ css << seconds(@delay) if @delay != 0
109
112
 
110
113
  css << @iteration_count
111
114
 
@@ -0,0 +1,96 @@
1
+ module Inkcite
2
+ class ImageMinifier
3
+
4
+ # Directory of optimized images
5
+ IMAGE_CACHE = 'images-optim'
6
+
7
+ # Path to ImageOption app on OS X
8
+ IMAGE_OPTIM_PATH = "/Applications/ImageOptim.app/Contents/MacOS/ImageOptim"
9
+
10
+ # Extension to put on files tha have been optimized already in the images/
11
+ # source folder
12
+ OPTIMIZED_EXTENSION = '.optimized'
13
+
14
+ def self.minify email, img_name, force=false
15
+
16
+ # Original, unoptimized source image
17
+ source_img = File.join(email.image_dir, img_name)
18
+
19
+ # Cached, optimized path for this image.
20
+ cache_path = email.project_file(IMAGE_CACHE)
21
+ cached_img = File.join(cache_path, File.basename(img_name))
22
+
23
+ unless force
24
+
25
+ # Get the last-modified date of the actual image. If the source
26
+ # image is newer than the cached version, we'll need to run it
27
+ # through optimization again, too.
28
+ cache_last_modified = Util.last_modified(cached_img)
29
+ source_last_modified = Util.last_modified(source_img)
30
+
31
+ # Nothing to do unless the image in the cache is older than the
32
+ # source or the config file.
33
+ return unless source_last_modified > cache_last_modified
34
+
35
+ end
36
+
37
+ # Make sure the image cache directory exists
38
+ FileUtils.mkpath(cache_path)
39
+
40
+ # Copy the original image to the cache where it can be processed.
41
+ FileUtils.copy(source_img, cached_img)
42
+
43
+ # Can only optimize the image if ImageOptim app is installed.
44
+ # @TODO Need to do the right thing on Windows.
45
+ if File.file?(IMAGE_OPTIM_PATH)
46
+
47
+ original_size = File.size(source_img)
48
+
49
+ # Check to see if there is a .optimized marker file indicating that a file
50
+ # shouldn't be further optimized.
51
+ if File.exists?("#{source_img}#{OPTIMIZED_EXTENSION}")
52
+ msg = "Copying #{img_name} #{Util.pretty_file_size(original_size)}"
53
+
54
+ compressed_size = original_size
55
+
56
+ else
57
+ msg = "Compressing #{img_name} #{Util.pretty_file_size(original_size)}"
58
+
59
+ Util.exec("#{IMAGE_OPTIM_PATH} #{cached_img}")
60
+
61
+ compressed_size = File.size(cached_img)
62
+ msg << " → #{Util.pretty_file_size(compressed_size)}"
63
+
64
+ end
65
+
66
+ # Get the final compressed size of the image so we can print the
67
+ # resulting compression ratio.
68
+ msg << " (#{self.compressed_percent(original_size, compressed_size)}%)"
69
+
70
+ Util.log msg
71
+ end
72
+
73
+
74
+ end
75
+
76
+ # Minifies all of the images in the provided email's project directory.
77
+ def self.minify_all email, force=false
78
+
79
+ images_path = File.join(email.image_dir, '*.*')
80
+
81
+ # Iterate through all of the images in the project and optimize them
82
+ # if necessary.
83
+ Dir.glob(images_path).each do |img|
84
+ next if img.to_s.end_with?(OPTIMIZED_EXTENSION)
85
+ self.minify(email, File.basename(img), force)
86
+ end
87
+
88
+ end
89
+
90
+ def self.compressed_percent original_size, compressed_size
91
+ ((1.0 - (compressed_size / original_size.to_f)) * 100).round(1)
92
+ end
93
+
94
+ end
95
+ end
96
+
@@ -196,10 +196,10 @@ module Inkcite
196
196
  clients = recipient_yml[:clients] || recipient_yml[:client]
197
197
  recipients[:to] = first_preview || clients
198
198
  recipients[:cc] = recipient_yml[:internal]
199
- when :internal
200
- recipients[:to] = recipient_yml[:internal]
201
199
  when :developer
202
200
  recipients[:to] = from
201
+ else # internal or any custom list
202
+ recipients[:to] = recipient_yml[list]
203
203
  end
204
204
 
205
205
  end
@@ -1,4 +1,4 @@
1
- require_relative 'image/image_minifier'
1
+ require_relative 'image_minifier'
2
2
 
3
3
  module Inkcite
4
4
  class Minifier
@@ -17,6 +17,7 @@ require_relative 'renderer/in_browser'
17
17
  require_relative 'renderer/increment'
18
18
  require_relative 'renderer/like'
19
19
  require_relative 'renderer/link'
20
+ require_relative 'renderer/list'
20
21
  require_relative 'renderer/litmus_analytics'
21
22
  require_relative 'renderer/lorem'
22
23
  require_relative 'renderer/mobile_image'
@@ -27,6 +28,7 @@ require_relative 'renderer/partial'
27
28
  require_relative 'renderer/preheader'
28
29
  require_relative 'renderer/property'
29
30
  require_relative 'renderer/redacted'
31
+ require_relative 'renderer/slant'
30
32
  require_relative 'renderer/snow'
31
33
  require_relative 'renderer/social'
32
34
  require_relative 'renderer/span'
@@ -71,6 +73,10 @@ module Inkcite
71
73
  # Remove unicode line break characters
72
74
  value.gsub!(/\u2028/, '')
73
75
 
76
+ # Nuclear option to remove invisible unicode characters injected
77
+ # when pasting text from MS Word or Google Docs.
78
+ value.gsub!(/[^\P{C}\n]+/u, '')
79
+
74
80
  value
75
81
  end
76
82
 
@@ -194,6 +200,7 @@ module Inkcite
194
200
  :'in-browser' => InBrowser.new,
195
201
  :include => Partial.new,
196
202
  :instagram => Social::Instagram.new,
203
+ :li => List.new,
197
204
  :like => Like.new,
198
205
  :litmus => LitmusAnalytics.new,
199
206
  :lorem => Lorem.new,
@@ -201,10 +208,12 @@ module Inkcite
201
208
  :'mobile-only' => MobileOnly.new,
202
209
  :'mobile-style' => MobileStyle.new,
203
210
  :'mobile-toggle-on' => MobileToggleOn.new,
211
+ :ol => List.new,
204
212
  :pintrest => Social::Pintrest.new,
205
213
  :preheader => Preheader.new,
206
214
  :r => Trademark.new('&reg;'),
207
215
  :redacted => Redacted.new,
216
+ :slant => Slant.new,
208
217
  :snow => Snow.new,
209
218
  :span => Span.new,
210
219
  :sparkle => Sparkle.new,
@@ -215,6 +224,7 @@ module Inkcite
215
224
  :'topic' => Topic.new,
216
225
  :'topic-list' => TopicList.new,
217
226
  :twitter => Social::Twitter.new,
227
+ :ul => List.new,
218
228
  :'video-preview' => VideoPreview.new
219
229
  }
220
230
 
@@ -5,6 +5,8 @@ module Inkcite
5
5
  # Constants for style and property names with dashes in them.
6
6
  BACKGROUND_COLOR = :'background-color'
7
7
  BACKGROUND_GRADIENT = :'background-gradient'
8
+ BACKGROUND_GRADIENT_POSITION = :'background-gradient-position'
9
+ BACKGROUND_GRADIENT_SHAPE = :'background-gradient-shape'
8
10
  BACKGROUND_IMAGE = :'background-image'
9
11
  BACKGROUND_REPEAT = :'background-repeat'
10
12
  BACKGROUND_POSITION = :'background-position'
@@ -16,7 +18,9 @@ module Inkcite
16
18
  BORDER_RADIUS = :'border-radius'
17
19
  BORDER_RIGHT = :'border-right'
18
20
  BORDER_SPACING = :'border-spacing'
21
+ BORDER_STYLE = :'border-style'
19
22
  BORDER_TOP = :'border-top'
23
+ BORDER_WIDTH = :'border-width'
20
24
  BOX_SHADOW = :'box-shadow'
21
25
  FONT_FAMILY = :'font-family'
22
26
  FONT_SIZE = :'font-size'
@@ -31,10 +35,13 @@ module Inkcite
31
35
  MARGIN_TOP = :'margin-top'
32
36
  MAX_HEIGHT = :'max-height'
33
37
  MAX_WIDTH = :'max-width'
38
+ MSO_HIDE = :'mso-hide'
39
+ MSO_PADDING_ALT = :'mso-padding-alt'
34
40
  PADDING_X = :'padding-x'
35
41
  PADDING_Y = :'padding-y'
36
42
  TEXT_ALIGN = :'text-align'
37
43
  TEXT_DECORATION = :'text-decoration'
44
+ TEXT_JUSTIFY = :'text-justify'
38
45
  TEXT_SHADOW = :'text-shadow'
39
46
  TEXT_SHADOW_BLUR = :'shadow-blur'
40
47
  TEXT_SHADOW_OFFSET = :'shadow-offset'
@@ -62,6 +69,14 @@ module Inkcite
62
69
  # Zero-width non-breaking character
63
70
  ZERO_WIDTH_NON_BREAKING_SPACE = '&#xfeff;'
64
71
 
72
+ # Constant for justified text alignment.
73
+ JUSTIFY = 'justify'
74
+
75
+ # For gradients
76
+ LINEAR = 'linear'
77
+ CIRCLE = 'circle'
78
+ RADIAL = 'radial'
79
+
65
80
  def render tag, opt, ctx
66
81
  raise "Not implemented: #{tag} #{opts}"
67
82
  end
@@ -151,7 +166,26 @@ module Inkcite
151
166
  center_color = bgcolor ? bgcolor : bggradient
152
167
  outer_color = bgcolor ? bggradient : Util.darken(bggradient)
153
168
 
154
- bggradient = %Q(radial-gradient(circle at center, #{center_color}, #{outer_color}))
169
+ # The default position of the gradient when not specified
170
+ # by the designer.
171
+ default_position = 'center'
172
+
173
+ shape = opt[BACKGROUND_GRADIENT_SHAPE] || LINEAR
174
+ if shape == LINEAR
175
+ type = LINEAR
176
+ shape = 'to '
177
+
178
+ # Linear gradients default to the bottom.
179
+ default_position = 'bottom'
180
+
181
+ else
182
+ type = RADIAL
183
+ shape = "#{shape} at "
184
+ end
185
+
186
+ position = detect(opt[BACKGROUND_GRADIENT_POSITION], default_position)
187
+
188
+ bggradient = %Q(#{type}-gradient(#{shape}#{position}, #{center_color}, #{outer_color}))
155
189
  end
156
190
 
157
191
  element.style[BACKGROUND_IMAGE] = bggradient
@@ -238,7 +272,18 @@ module Inkcite
238
272
  def mix_text_align element, opt, ctx
239
273
 
240
274
  align = detect(opt[:align] || opt[TEXT_ALIGN])
241
- element.style[TEXT_ALIGN] = align unless none?(align)
275
+ return if none?(align)
276
+
277
+ element.style[TEXT_ALIGN] = align
278
+
279
+ mix_text_justify(element, opt, ctx) if align == JUSTIFY
280
+
281
+ end
282
+
283
+ def mix_text_justify element, opt, ctx
284
+
285
+ text_justify = opt[TEXT_JUSTIFY]
286
+ element.style[TEXT_JUSTIFY] = text_justify unless none?(text_justify)
242
287
 
243
288
  end
244
289