inkcite 1.15.0 → 1.16.0

Sign up to get free protection for your applications and to get access to all the features.
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