inkcite 1.7.0 → 1.8.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9d4ff329598b10063f608482495b1067089c890f
4
- data.tar.gz: 382ad87478df473ad76bda20a7fb306e125c65b5
3
+ metadata.gz: d4d3044cbc8db45ebd662ab451d759ef1f0aa25d
4
+ data.tar.gz: e87b26d745c307427fd53c7f1e55c19e242f3aaa
5
5
  SHA512:
6
- metadata.gz: 15a0cb8480b9e0abf92c31e2304b3f65baa64b450af06bd66061e3ca18f217645239bf5bedac1a74b0b5bea43a28e6a115de350c62ea30818e9a9b58bb0bddee
7
- data.tar.gz: 7313d13e0033675536c3255946bde8cd37c5eef54fbd50c9abe8787a97cf77f76fde38f7bd1e2090f967b8ba4f512da4daa16bd132db776cb8d566c2ed643932
6
+ metadata.gz: efb49479b68f7745f54902ea4d488c80532345baf772f74f6c939c22a77247c8f9635f4ac73cbf765cab9b3b96319c5af832f06f53c072e5ffd3541cda5a7460
7
+ data.tar.gz: 40b7fcc6f1bf820be7bfe5b890730a831b08f48fdd390061584c6808164b19998f1f038a1ad33740365f38689498e192d61f2f4d8719cb6280a235e9fd68b66a
data/README.md CHANGED
@@ -5,12 +5,13 @@ Like [Middleman] is to static web sites, Inkcite makes it easy for email
5
5
  developers to keep their code DRY (don’t repeat yourself) and integrate
6
6
  versioning, testing and minification into their workflow.
7
7
 
8
- * Easy, flexible templates, variables and helpers
9
- * ERB for easy A/B testing and versioning
8
+ * Easy, flexible templates, variables and Helpers
9
+ * ERB for dynamic content and easy A/B Testing and Versioning
10
10
  * Automatic link tagging and tracking
11
- * [Litmus] integration for compatibility
12
- * Preview distribution lists
13
- * Content inclusion/exclusion rules
11
+ * [Litmus]-integrated compatibility testing and analytics
12
+ * Email preview distribution lists
13
+ * Automatic image optimization using ImageOptim
14
+ * Failsafe rules to double-check your work
14
15
 
15
16
  ## Installation
16
17
 
@@ -106,7 +107,7 @@ developer questions in a timely manner.
106
107
 
107
108
  ## License
108
109
 
109
- Copyright (c) 2014 Jeffrey D. Hoffman. MIT Licensed, see [LICENSE] for
110
+ Copyright (c) 2014-2015 Jeffrey D. Hoffman. MIT Licensed, see [LICENSE] for
110
111
  details.
111
112
 
112
113
  [Middleman]: http://middlemanapp.com
data/inkcite.gemspec CHANGED
@@ -29,6 +29,7 @@ Gem::Specification.new do |spec|
29
29
  spec.add_dependency 'faker'
30
30
  spec.add_dependency 'guard'
31
31
  spec.add_dependency 'guard-livereload'
32
+ spec.add_dependency 'htmlbeautifier'
32
33
  spec.add_dependency 'image_optim'
33
34
  spec.add_dependency 'image_optim_pack'
34
35
  spec.add_dependency 'litmus'
@@ -9,6 +9,10 @@ module Inkcite
9
9
  option :archive,
10
10
  :aliases => '-a',
11
11
  :desc => 'The name of the archive to compress final assets into'
12
+ option :environment,
13
+ :aliases => '-e',
14
+ :desc => 'The environment (development, preview or production) to build',
15
+ :default => :production
12
16
  option :force,
13
17
  :aliases => '-f',
14
18
  :desc => 'Build even if there are errors (not recommended)',
@@ -16,10 +20,7 @@ module Inkcite
16
20
 
17
21
  def build
18
22
  require_relative 'build'
19
- Cli::Build.invoke(email, {
20
- :archive => options['archive'],
21
- :force => options['force']
22
- })
23
+ Cli::Build.invoke(email, options)
23
24
  end
24
25
 
25
26
  desc 'init NAME [options]', 'Initialize a new email project in the NAME directory'
@@ -7,24 +7,23 @@ module Inkcite
7
7
  errors = false
8
8
 
9
9
  # Don't allow production files to be built if there are errors.
10
- email.views(:production) do |ev|
10
+ email.views(opts[:environment]) do |ev|
11
11
 
12
12
  ev.render!
13
+ next if ev.errors.blank?
13
14
 
14
- if !ev.errors.blank?
15
- puts "The #{ev.version} version (#{ev.format}) has #{ev.errors.size} errors:"
16
- puts " - #{ev.errors.join("\n - ")}"
17
- errors = true
18
- end
15
+ puts "The #{ev.version} version (#{ev.format}) has #{ev.errors.size} errors:"
16
+ puts " - #{ev.errors.join("\n - ")}"
17
+ errors = true
19
18
 
20
19
  end
21
20
 
22
- abort("Fix errors or use --force to build") if errors && !opts[:force]
21
+ abort('Fix errors or use --force to build') if errors && !opts[:force]
23
22
 
24
23
  # First, compile all assets to the build directory.
25
24
  build_to_dir email, opts
26
25
 
27
- # No archive? Build to files instead.
26
+ # Compress the directory into an archive if so desired.
28
27
  archive = opts[:archive]
29
28
  build_archive(email, opts) unless archive.blank?
30
29
 
@@ -102,7 +101,7 @@ module Inkcite
102
101
  email.optimize_images
103
102
 
104
103
  # For each of the production views, build the HTML and links files.
105
- email.views(:production) do |ev|
104
+ email.views(opts[:environment]) do |ev|
106
105
 
107
106
  File.open(File.join(build_html_to, ev.file_name), 'w') { |f| ev.write(f) }
108
107
 
@@ -113,7 +112,8 @@ module Inkcite
113
112
 
114
113
  # Copy all of the source images into the build directory in preparation
115
114
  # for optimization
116
- FileUtils.cp_r(File.join(email.optimized_image_dir, '.'), build_images_to)
115
+ build_images_from = email.optimized_image_dir
116
+ FileUtils.cp_r(File.join(build_images_from, '.'), build_images_to) if File.exists?(build_images_from)
117
117
 
118
118
  end
119
119
 
@@ -19,7 +19,7 @@ module Inkcite
19
19
  is_empty = opts[:empty]
20
20
 
21
21
  # True if we're initializing a project from the built-in files.
22
- is_new = opts[:from].blank?
22
+ is_new = from_path.blank?
23
23
  if is_new
24
24
 
25
25
  # Use the default, bundled path if a from-path wasn't specified.
@@ -51,7 +51,7 @@ module Inkcite
51
51
 
52
52
  # If the example email is required, switch to the example root and
53
53
  # copy the files within over the existing files.
54
- unless is_empty
54
+ if is_new && !is_empty
55
55
  from_path = File.join(Inkcite.asset_path, 'example')
56
56
  FileUtils.cp_r(File.join(from_path, '.'), full_init_path)
57
57
  puts 'Copied example email files'
@@ -1,5 +1,6 @@
1
1
  require 'guard'
2
2
  require 'guard/commander'
3
+ require 'htmlbeautifier'
3
4
  require 'rack'
4
5
  require 'rack-livereload'
5
6
  require 'webrick'
@@ -123,6 +124,10 @@ module Inkcite
123
124
 
124
125
  html = view.render!
125
126
 
127
+ # If we're rendering the development version of the email, beautify the
128
+ # output so that
129
+ html = HtmlBeautifier.beautify(html) if view.development?
130
+
126
131
  unless view.errors.blank?
127
132
  error_count = view.errors.count
128
133
  puts "#{ts} #{error_count} error#{'s' if error_count > 1} or warning#{'s' if error_count > 1}:"
data/lib/inkcite/email.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  module Inkcite
2
2
  class Email
3
3
 
4
+ BROWSER_VERSION = :'browser-version'
4
5
  CACHE_BUST = :'cache-bust'
5
6
  IMAGE_HOST = :'image-host'
6
7
  IMAGE_PLACEHOLDERS = :'image-placeholders'
@@ -28,7 +29,11 @@ module Inkcite
28
29
 
29
30
  def formats env
30
31
 
31
- f = [ :email, :browser ]
32
+ # Inkcite is always capable of producing an email version of
33
+ # the project.
34
+ f = [ :email ]
35
+
36
+ f << :browser if config[BROWSER_VERSION] == true
32
37
 
33
38
  # Need to make sure a source.txt exists before we can include
34
39
  # it in the list of known formats.
@@ -61,7 +66,7 @@ module Inkcite
61
66
  end
62
67
 
63
68
  def optimize_images?
64
- config[Inkcite::Email::OPTIMIZE_IMAGES] == true
69
+ config[OPTIMIZE_IMAGES] == true
65
70
  end
66
71
 
67
72
  # Returns the directory that optimized, compressed images
@@ -102,7 +107,9 @@ module Inkcite
102
107
  version = (version || versions.first).to_sym
103
108
 
104
109
  raise "Unknown environment \"#{environment}\" - must be one of #{ENVIRONMENTS.join(',')}" unless ENVIRONMENTS.include?(environment)
105
- raise "Unknown format \"#{format}\" - must be one of #{FORMATS.join(',')}" unless FORMATS.include?(format)
110
+
111
+ _formats = formats(environment)
112
+ raise "Unknown format \"#{format}\" - must be one of #{_formats.join(',')}" unless _formats.include?(format)
106
113
  raise "Unknown version: \"#{version}\" - must be one of #{versions.join(',')}" unless versions.include?(version)
107
114
 
108
115
  # Instantiate a new view of this email with the desired view and
@@ -130,9 +137,6 @@ module Inkcite
130
137
 
131
138
  private
132
139
 
133
- # Allowed formats.
134
- FORMATS = [ :browser, :email, :text ].freeze
135
-
136
140
  # Name of the property controlling the meta data file name and
137
141
  # the default file name.
138
142
  META_FILE_NAME = :'meta-file'
@@ -24,6 +24,7 @@ module Inkcite
24
24
  MARGIN_LEFT = :'margin-left'
25
25
  MARGIN_RIGHT = :'margin-right'
26
26
  MARGIN_TOP = :'margin-top'
27
+ MAX_WIDTH = :'max-width'
27
28
  PADDING_X = :'padding-x'
28
29
  PADDING_Y = :'padding-y'
29
30
  TEXT_ALIGN = :'text-align'
@@ -78,6 +79,10 @@ module Inkcite
78
79
  Renderer.hex(color)
79
80
  end
80
81
 
82
+ def if_mso html
83
+ %Q(<!--[if mso]>#{html.to_s}<![endif]-->)
84
+ end
85
+
81
86
  def none? val
82
87
  val.blank? || val == NONE
83
88
  end
@@ -92,6 +97,21 @@ module Inkcite
92
97
 
93
98
  end
94
99
 
100
+ def mix_border element, opt
101
+
102
+ border = opt[:border]
103
+ element.style[:border] = border unless border.blank?
104
+
105
+ # Iterate through each of the possible borders and apply them individually
106
+ # to the style if they are defined.
107
+ DIRECTIONS.each do |dir|
108
+ key = :"border-#{dir}"
109
+ border = opt[key]
110
+ element.style[key] = border unless border.blank? || border == NONE
111
+ end
112
+
113
+ end
114
+
95
115
  def mix_font element, opt, ctx, parent=nil
96
116
 
97
117
  # Always ensure we have a parent to inherit from.
@@ -8,16 +8,24 @@ module Inkcite
8
8
 
9
9
  div = Element.new('div')
10
10
 
11
+ width = opt[:width]
12
+ div.style[:width] = px(width) unless width.blank?
13
+
11
14
  height = opt[:height].to_i
12
15
  div.style[:height] = px(height) if height > 0
13
16
 
17
+ mix_background div, opt
18
+ mix_font div, opt, ctx
19
+
14
20
  # Text alignment - left, right, center.
15
21
  align = opt[:align]
16
22
  div.style[TEXT_ALIGN] = align unless none?(align)
17
23
 
18
- mix_font div, opt, ctx
24
+ valign = opt[:valign]
25
+ div.style[VERTICAL_ALIGN] = valign unless valign.blank?
19
26
 
20
- mix_background div, opt
27
+ display = opt[:display]
28
+ div.style[:display] = display unless display.blank?
21
29
 
22
30
  mix_responsive div, opt, ctx
23
31
 
@@ -13,7 +13,19 @@ module Inkcite
13
13
  # True if the tag self-closes as in "<img .../>"
14
14
  @self_close = att.delete(:self_close) == true
15
15
 
16
+ # For caller convenience, accept a style hash from the attributes
17
+ # or initialize it here.
18
+ @styles = att.delete(:style) || {}
19
+
20
+ end
21
+
22
+ # I found myself doing a lot of Element.new('tag', { }).to_s + 'more html'
23
+ # so this method makes it easier by allowing elements to be added to
24
+ # strings.
25
+ def + html
26
+ to_s << html.to_s
16
27
  end
28
+ alias_method :concat, :+
17
29
 
18
30
  def [] key
19
31
  @att[key]
@@ -52,7 +64,7 @@ module Inkcite
52
64
  end
53
65
 
54
66
  def style
55
- @styles ||= {}
67
+ @styles
56
68
  end
57
69
 
58
70
  def to_s
@@ -21,6 +21,7 @@ module Inkcite
21
21
  # activate but those read from footnotes.tsv are inactive until
22
22
  # referenced in the source.
23
23
  attr_accessor :active
24
+ alias_method :active?, :active
24
25
 
25
26
  def initialize id, symbol, text, active=true
26
27
  @id = id
@@ -94,8 +95,8 @@ module Inkcite
94
95
  # Grab the last numeric footnote that was specified and, assuming
95
96
  # there is one, increment the count. Otherwise, start the count
96
97
  # off at one.
97
- last_instance = ctx.footnotes.select(&:numeric?).last
98
- instance.symbol = last_instance.nil? ? 1 : last_instance.symbol.to_i + 1
98
+ last_instance = ctx.footnotes.select { |fn| fn.numeric? && fn.active? }.collect(&:number).max.to_i
99
+ instance.symbol = last_instance + 1
99
100
 
100
101
  end
101
102
 
@@ -14,6 +14,7 @@ module Inkcite
14
14
  img[:src] = image_url(opt[:src], opt, ctx)
15
15
 
16
16
  mix_background img, opt
17
+ mix_border img, opt
17
18
 
18
19
  # Check to see if there is alt text specified for this image. We are
19
20
  # testing against nil because sometimes the author desires an empty
@@ -36,6 +37,9 @@ module Inkcite
36
37
 
37
38
  mix_font img, opt, ctx
38
39
 
40
+ text_align = opt[TEXT_ALIGN]
41
+ img.style[TEXT_ALIGN] = text_align unless text_align.blank?
42
+
39
43
  end
40
44
 
41
45
  end
@@ -75,9 +79,28 @@ module Inkcite
75
79
 
76
80
  mobile = opt[:mobile]
77
81
 
78
- # Check to see if this image is inside of a mobile-image declaration.
79
- # If so, the image defaults to hide on mobile.
80
- mobile = HIDE if mobile.blank? && !ctx.parent_opts(:mobile_image).blank?
82
+ # Fluid-Hybrid responsive images courtesy of @moonstrips and @CourtFantinato.
83
+ # http://webdesign.tutsplus.com/tutorials/creating-a-future-proof-responsive-email-without-media-queries--cms-23919#comment-2074740905
84
+ if mobile == FLUID
85
+
86
+ # Set the inline styles of the image to scale with aspect ratio
87
+ # intact up to the maximum width of the image itself.
88
+ img.style[MAX_WIDTH] = px(opt[:width])
89
+ img.style[:width] = '100%'
90
+ img.style[:height] = 'auto'
91
+
92
+ # Leave the explicit width attribute set (this prevents Outlook from
93
+ # blowing up) but clear the height attribute as Gmail images will not
94
+ # maintain aspect ratio if present.
95
+ img[:height] = nil
96
+
97
+ else
98
+
99
+ # Check to see if this image is inside of a mobile-image declaration.
100
+ # If so, the image defaults to hide on mobile.
101
+ mobile = HIDE if mobile.blank? && !ctx.parent_opts(:mobile_image).blank?
102
+
103
+ end
81
104
 
82
105
  mix_responsive img, opt, ctx, mobile
83
106
 
@@ -2,15 +2,17 @@ module Inkcite
2
2
  module Renderer
3
3
  class Responsive < Base
4
4
 
5
- BUTTON = 'button'
6
- DROP = 'drop'
7
- FILL = 'fill'
8
- HIDE = 'hide'
9
- IMAGE = 'img'
10
- SHOW = 'show'
11
- SWITCH = 'switch'
12
- SWITCH_UP = 'switch-up'
13
- TOGGLE = 'toggle'
5
+ BUTTON = 'button'
6
+ DROP = 'drop'
7
+ FILL = 'fill'
8
+ FLUID = 'fluid'
9
+ FLUID_DROP = 'fluid-drop'
10
+ HIDE = 'hide'
11
+ IMAGE = 'img'
12
+ SHOW = 'show'
13
+ SWITCH = 'switch'
14
+ SWITCH_UP = 'switch-up'
15
+ TOGGLE = 'toggle'
14
16
 
15
17
  # For elements that take on different background properties
16
18
  # when they go responsive
@@ -180,6 +182,12 @@ module Inkcite
180
182
 
181
183
  protected
182
184
 
185
+ # Returns true if the mobile klass provided matches any of the
186
+ # Fluid-Hybrid classes.
187
+ def is_fluid? mobile
188
+ mobile == FLUID || mobile == FLUID_DROP
189
+ end
190
+
183
191
  def mix_font element, opt, ctx, parent=nil
184
192
 
185
193
  # Let the super class do its thing and grab the name of the font
@@ -215,6 +223,11 @@ module Inkcite
215
223
  # Nothing to do if there is no class specified.s
216
224
  return nil if klass.blank?
217
225
 
226
+ # The Fluid-Hybrid klass is also ignored because it doesn't involve
227
+ # media queries - aborting early to avoid the "missing mobile class"
228
+ # warning normally generated.
229
+ return nil if is_fluid?(klass)
230
+
218
231
  mq = ctx.media_query
219
232
 
220
233
  # The element's tag - e.g. table, td, etc.
@@ -226,7 +239,7 @@ module Inkcite
226
239
 
227
240
  id = opt[:id]
228
241
  if id.blank?
229
- ctx.errors 'Mobile elements with toggle behavior require an ID attribute', { :tag => tag} if id.blank?
242
+ ctx.errors 'Mobile elements with toggle behavior require an ID attribute', { :tag => tag } if id.blank?
230
243
 
231
244
  else
232
245
 
@@ -4,60 +4,144 @@ module Inkcite
4
4
 
5
5
  def render tag, opt, ctx
6
6
 
7
+ html = ''
8
+
9
+ # We're either going to be pushing a newly opened table onto this stack
10
+ # or we're popping the open opts off of it.
7
11
  tag_stack = ctx.tag_stack(:table)
8
12
 
9
13
  if tag == CLOSE_TABLE
10
14
 
11
- # Remove this table from the stack of previously open tags.
12
- tag_stack.pop
15
+ # Pop the opts used when the table was originally opened. Then grab the
16
+ # mobile attribute. If this is a fluid table, we'll need to do close some
17
+ # of the additional tags injected when it was opened.
18
+ open_opt = tag_stack.pop
19
+ open_mobile = open_opt[:mobile]
13
20
 
14
- return '</tr></table>'
21
+ # If the table was declared as Fluid-Hybrid Drop, then there are some additional
22
+ # elements that need to be closed before the regular row-table closure that
23
+ # the Table helper normally produces.
24
+ if open_mobile == FLUID_DROP
15
25
 
16
- end
26
+ # Close the interior conditional table for Outlook that contains the floating blocks.
27
+ html << if_mso('</tr></table>')
17
28
 
18
- # Push this table onto the stack which will make it's declaration
19
- # available to its child TDs.
20
- tag_stack << opt
29
+ # Close what @campaignmonitor calls the "secret weapon" cell that typically aligns
30
+ # the text horizontally and vertically aligns the floating elements.
31
+ html << '</td>' # Styled
32
+ end
21
33
 
22
- table = Element.new(tag, { :border => 0, :cellspacing => 0 })
34
+ # Normal Inkcite Helper close HTML.
35
+ html << '</tr></table>'
23
36
 
24
- # Inherit base cell attributes - border, background color and image, etc.
25
- mix_all table, opt, ctx
37
+ # Close the conditional table for Output that contains the entire fluid layout.
38
+ html << if_mso('</td></tr></table>') if is_fluid?(open_mobile)
26
39
 
27
- # Text shadowing
28
- mix_text_shadow table, opt, ctx
40
+ else
29
41
 
30
- # Conveniently accept padding (easier to type and consistent with CSS)or
31
- # cellpadding which must always be declared.
32
- table[:cellpadding] = (opt[:padding] || opt[:cellpadding]).to_i
42
+ # Push this table onto the stack which will make it's declaration
43
+ # available to its child TDs.
44
+ tag_stack << opt
33
45
 
34
- # Conveniently accept both float and align to mean the same thing.
35
- align = opt[:align] || opt[:float]
36
- table[:align] = align unless align.blank?
46
+ table = Element.new(tag, { :border => 0, :cellspacing => 0 })
37
47
 
38
- border_radius = opt[BORDER_RADIUS].to_i
39
- table.style[BORDER_RADIUS] = px(border_radius) if border_radius > 0
48
+ # Grab the responsive mobile klass that is assigned to this table, if any.
49
+ mobile = opt[:mobile]
40
50
 
41
- border_collapse = opt[BORDER_COLLAPSE]
42
- table.style[BORDER_COLLAPSE] = border_collapse unless border_collapse.blank?
51
+ # Check if fluid-drop has been specified. This will force a lot more HTML to
52
+ # be produced for this table and its child TDs.
53
+ is_fluid_drop = mobile == FLUID_DROP
43
54
 
44
- # Apply margins.
45
- mix_margins table, opt, ctx
55
+ # Inherit base cell attributes - border, background color and image, etc.
56
+ mix_all table, opt, ctx
57
+ mix_margins table, opt, ctx
58
+ mix_text_shadow table, opt, ctx
46
59
 
47
- mobile = opt[:mobile]
60
+ # Conveniently accept padding (easier to type and consistent with CSS) or
61
+ # cellpadding which must always be declared.
62
+ #
63
+ # If Fluid-Drop is enabled, padding is always zero at this top-level table
64
+ # and will be applied in the TD renderer when it creates a new table to
65
+ # wrap itself in.
66
+ table[:cellpadding] = is_fluid_drop ? 0 : get_padding(opt)
48
67
 
49
- # When a Table is configured to have it's cells DROP then it
50
- # actually needs to FILL on mobile and it's child Tds will
51
- # be DROP'd. Override the local mobile klass so the child Tds
52
- # see the parent as DROP.
53
- mobile = FILL if mobile == DROP || mobile == SWITCH
68
+ # Conveniently accept both float and align to mean the same thing.
69
+ align = opt[:align] || opt[:float]
70
+ table[:align] = align unless align.blank?
54
71
 
55
- mix_responsive table, opt, ctx, mobile
72
+ border_radius = opt[BORDER_RADIUS].to_i
73
+ table.style[BORDER_RADIUS] = px(border_radius) if border_radius > 0
56
74
 
57
- table.to_s + '<tr>'
58
- end
75
+ border_collapse = opt[BORDER_COLLAPSE]
76
+ table.style[BORDER_COLLAPSE] = border_collapse unless border_collapse.blank?
77
+
78
+
79
+ # For both fluid and fluid-drop share certain setup which is performed here.
80
+ if is_fluid?(mobile)
81
+
82
+ # Width must always be specified on fluid tables, like it is for images.
83
+ # Warn the designer if a width has not been supplied in pixels.
84
+ width = opt[:width].to_i
85
+ ctx.error("Width is a required attribute when '#{mobile}' is applied to a {table}", opt) unless width > 0
86
+
87
+ # Fluid table method courtesy of @campaignmonitor - assign the max-width in
88
+ # pixels and set the normal width to 100%.
89
+ # https://www.campaignmonitor.com/blog/email-marketing/2014/07/creating-a-centred-responsive-design-without-media-queries/
90
+ table.style[MAX_WIDTH] = px(width)
91
+ table[:width] = '100%'
92
+
93
+ # Outlook (and Lotus Notes, if you can believe it) doesn't support max-width
94
+ # so we need to wrap the entire fluid table in a conditional table that
95
+ # ensures layout displays within the actual maximum pixels width.
96
+ html << if_mso(Element.new('table', {
97
+ :align => opt[:align], :border => 0, :cellspacing => 0, :cellpadding => 0, :width => opt[:width].to_i
98
+ }) + '<tr><td>')
99
+
100
+ elsif mobile == DROP || mobile == SWITCH
101
+
102
+ # When a Table is configured to have it's cells DROP then it
103
+ # actually needs to FILL on mobile and it's child Tds will
104
+ # be DROP'd. Override the local mobile klass so the child Tds
105
+ # see the parent as DROP.
106
+ mobile = FILL
107
+
108
+ end
59
109
 
60
- private
110
+ mix_responsive table, opt, ctx, mobile
111
+
112
+ html << table.to_s
113
+ html << '<tr>'
114
+
115
+ if is_fluid_drop
116
+
117
+ # Fluid-Drop tables need a default alignment specified which is inherited
118
+ # by the child TD elements if not otherwise specified.
119
+ #
120
+ # 11/8/2015: For reasons I don't understand, if the table is not valigned
121
+ # middle by default, then we lose the ability to valign-middle individual
122
+ # TD children. So, if we force 'middle' here, then the TDs can override
123
+ # with 'top' or 'bottom' alignment when desired.
124
+ valign = opt[:valign] ||= 'middle'
125
+
126
+ # According to @campaignmonitor this is the secret weapon of Fluid-Hyrbid
127
+ # drop which wraps the floating elements and centers them appropriately.
128
+ # https://www.campaignmonitor.com/blog/email-marketing/2014/07/creating-a-centred-responsive-design-without-media-queries/
129
+ #
130
+ # The zero-size font addresses a rendering problem in Outlook:
131
+ # https://css-tricks.com/fighting-the-space-between-inline-block-elements/
132
+ html << Element.new('td', :style => { TEXT_ALIGN => :center, VERTICAL_ALIGN => opt[:valign], FONT_SIZE => 0 }).to_s
133
+
134
+ # Lastly, Outlook needs yet another conditional table that will be used
135
+ # to contain the floating blocks. The TD elements are generated by
136
+ # each of the columns within this Fluid-Drop table.
137
+ html << if_mso(Element.new('table', :width => '100%', :align => :center, :cellpadding => 0, :cellspacing => 0, :border => 0) + '<tr>')
138
+
139
+ end
140
+
141
+ end
142
+
143
+ html
144
+ end
61
145
 
62
146
  CLOSE_TABLE = '/table'
63
147
 
@@ -4,10 +4,16 @@ module Inkcite
4
4
 
5
5
  protected
6
6
 
7
+ # Returns cellpadding specified from the provided attributes by
8
+ # Checking both :padding and :cellpadding.
9
+ def get_padding opt
10
+ (opt[:padding] || opt[:cellpadding]).to_i
11
+ end
12
+
7
13
  def mix_all element, opt, ctx
8
14
 
9
15
  mix_background element, opt, ctx
10
- mix_border element, opt, ctx
16
+ mix_border element, opt
11
17
  mix_dimensions element, opt, ctx
12
18
 
13
19
  end
@@ -67,21 +73,6 @@ module Inkcite
67
73
 
68
74
  end
69
75
 
70
- def mix_border element, opt, ctx
71
-
72
- border = opt[:border]
73
- element.style[:border] = border unless border.blank?
74
-
75
- # Iterate through each of the possible borders and apply them individually
76
- # to the style if they are defined.
77
- DIRECTIONS.each do |dir|
78
- key = :"border-#{dir}"
79
- border = opt[key]
80
- element.style[key] = border unless border.blank? || border == NONE
81
- end
82
-
83
- end
84
-
85
76
  def mix_dimensions element, opt, ctx
86
77
 
87
78
  # Not taking .to_i because we want to accept both integer values
@@ -4,79 +4,171 @@ module Inkcite
4
4
 
5
5
  def render tag, opt, ctx
6
6
 
7
+ html = ''
8
+
9
+ # Tracks the depth of currently open TD elements.
7
10
  tag_stack = ctx.tag_stack(:td)
8
11
 
12
+ # Grab the attributes of the parent table so that the TD can inherit
13
+ # specific values like padding, valign, responsiveness, etc.
14
+ table_opt = ctx.parent_opts(:table)
15
+
16
+ # Check to see if the parent table was set to fluid-drop which causes
17
+ # the table cells to be wrapped in <div> elements and floated to
18
+ # cause them to display more responsively on Android Mail and Gmail apps.
19
+ #
20
+ # Fluid-Hybrid TD courtesy of @moonstrips and our friends at Campaign Monitor
21
+ # https://www.campaignmonitor.com/blog/email-marketing/2014/07/creating-a-centred-responsive-design-without-media-queries/
22
+ is_fluid_drop = table_opt[:mobile] == FLUID_DROP
23
+
9
24
  if tag == CLOSE_TD
25
+
26
+ # Retrieve the opts that were used to open this TD. We'll need them to
27
+ # check for the private _fluid_drop attribute.
10
28
  tag_stack.pop
11
- return '</td>'
12
- end
13
29
 
14
- # Push this tag onto the stack so that child elements (e.g. links)
15
- # can have access to its attributes.
16
- tag_stack << opt
30
+ # Normal HTML produced by the Helper to close the cell.
31
+ html << '</td>'
17
32
 
18
- # Grab the attributes of the parent table so that the TD can inherit
19
- # specific values like padding, valign, responsiveness, etc.
20
- parent = ctx.parent_opts(:table)
21
-
22
- td = Element.new('td')
23
-
24
- # Inherit base cell attributes - border, background color and image, etc.
25
- mix_all td, opt, ctx
26
-
27
- # Force the td to collapse to a single pixel to support images that
28
- # are less than 15 pixels.
29
- opt.merge!({
30
- :font => NONE,
31
- :color => NONE,
32
- FONT_SIZE => 1,
33
- LINE_HEIGHT => 1
34
- }) unless opt[:flush].blank?
35
-
36
- # It is a best-practice to declare the same padding on all cells in a
37
- # table. Check to see if padding was declared on the parent.
38
- padding = parent[:padding].to_i
39
- td.style[:padding] = px(padding) if padding > 0
40
-
41
- # Custom handling for text align on TDs rather than Base's mix_text_align
42
- # because if possible, using align= rather than a style keeps emails
43
- # smaller. But for left-aligned text, you gotta use a style because
44
- # you know, Outlook.
45
- align = opt[:align]
46
- unless align.blank?
47
- td[:align] = align
48
-
49
- # Must use style to reinforce left-align text in certain email clients.
50
- # All other alignments are accepted naturally.
51
- td.style[TEXT_ALIGN] = align if align == LEFT
33
+ # If the td was originally opened with fluid-drop, we need to do a fair
34
+ # bit of cleanup...
35
+ if is_fluid_drop
52
36
 
53
- end
37
+ # Close the
38
+ html << '</tr></table>'
54
39
 
55
- valign = detect(opt[:valign], parent[:valign])
56
- td[:valign] = valign unless valign.blank?
40
+ # Close the floating, responsive div.
41
+ html << '{/div}'
57
42
 
58
- rowspan = opt[:rowspan].to_i
59
- td[:rowspan] = rowspan if rowspan > 0
43
+ # Close the conditional cell
44
+ html << if_mso('</td>')
60
45
 
61
- mix_font td, opt, ctx, parent
46
+ end
62
47
 
63
- mobile = opt[:mobile]
64
- if mobile.blank?
48
+ else
65
49
 
66
- # If the cell doesn't define it's own responsive behavior, check to
67
- # see if it inherits from its parent table. DROP and SWITCH declared
68
- # at the table-level descend to their tds.
69
- pm = parent[:mobile]
70
- mobile = pm if pm == DROP || pm == SWITCH
50
+ # Push this tag onto the stack so that child elements (e.g. links)
51
+ # can have access to its attributes.
52
+ tag_stack << opt
71
53
 
72
- end
54
+ td = Element.new('td')
55
+
56
+ # Check to see if a width has been specified for this element. The
57
+ # width is critical to Fluid-Hybrid drop.
58
+ width = opt[:width].to_i
59
+
60
+ # Check for vertical alignment applied to either the TD or to the
61
+ # parent Table.
62
+ valign = detect(opt[:valign], table_opt[:valign])
63
+ td[:valign] = valign unless valign.blank?
64
+
65
+ # It is a best-practice to declare the same padding on all cells in a
66
+ # table. Check to see if padding was declared on the parent.
67
+ padding = get_padding(table_opt)
68
+ td.style[:padding] = px(padding) if padding > 0
69
+
70
+ mobile = opt[:mobile]
71
+
72
+ # Need to handle Fluid-Drop HTML injection here before the rest of the
73
+ # TD is formalized. Fluid-Drop removes the width attribute of the cell
74
+ # as it is wrapped in a 100%-width table.
75
+ if is_fluid_drop
76
+
77
+ # Width must be specified for Fluid-Drop cells. Vertical-alignment is
78
+ # also important but should have been preset by the Table Helper if it
79
+ # was omitted by the designer.
80
+ ctx.error("Width is a required attribute when #{FLUID_DROP} is specified", opt) unless width > 0
81
+ ctx.error("Vertical alignment should be specified when #{FLUID_DROP} is specified", opt) if valign.blank?
82
+
83
+ # Conditional Outlook cell to prevent the 100%-wide table within from
84
+ # stretching beyond the max-width. Also, valign necessary to get float
85
+ # elements to align properly.
86
+ html << if_mso(Element.new('td', :width => width, :valign => valign))
87
+
88
+ # Per @campaignmonitor, the secret to the Fluid-Drop trick is to wrap the
89
+ # floating table in a div with "display: inline-block" - which means that
90
+ # they'll obey the text-align property on the parent cell (text-align affects
91
+ # all inline or inline-block elements in a container).
92
+ # https://www.campaignmonitor.com/blog/email-marketing/2014/07/creating-a-centred-responsive-design-without-media-queries/
93
+
94
+ div_mobile = mobile == HIDE ? HIDE : FILL
95
+ html << %Q({div width=#{width} display=inline-block valign=#{valign} mobile="#{div_mobile}"})
73
96
 
74
- mix_responsive td, opt, ctx, mobile
97
+ # One last wrapper table within the div. This 100%-wide table is also where any
98
+ # padding applied to the elements belongs.
99
+ html << Element.new('table', :cellpadding => padding, :cellspacing => 0, :border => 0, :width => '100%').to_s
100
+ html << '<tr>'
75
101
 
76
- #outlook-bg <!-&#45;&#91;if gte mso 9]>[n]<v:rect style="width:%width%px;height:%height%px;" strokecolor="none"><v:fill type="tile" src="%src%" /></v:fill></v:rect><v:shape id="theText[rnd]" style="position:absolute;width:%width%px;height:%height%px;margin:0;padding:0;%style%">[n]<!&#91;endif]&#45;->
77
- #/outlook-bg <!-&#45;&#91;if gte mso 9]></v:shape><!&#91;endif]&#45;->
102
+ # Remove the width attribute from the TDs declaration.
103
+ opt.delete(:width)
104
+
105
+ # The TD nested within the floating div and additional table will inherit center-aligned
106
+ # text which means fluid-drop cells would have a default layout inconsistent with a regular
107
+ # TD - which will typically be left-aligned. So, unless otherwise specified, presume that
108
+ # the TD should have left-aligned text.
109
+ opt[:align] = 'left' if opt[:align].blank?
110
+
111
+ mobile = ''
112
+
113
+ end
114
+
115
+ # Inherit base cell attributes - border, background color and image, etc.
116
+ mix_all td, opt, ctx
117
+
118
+ # Force the td to collapse to a single pixel to support images that
119
+ # are less than 15 pixels.
120
+ opt.merge!({
121
+ :font => NONE,
122
+ :color => NONE,
123
+ FONT_SIZE => 1,
124
+ LINE_HEIGHT => 1
125
+ }) unless opt[:flush].blank?
126
+
127
+ # Custom handling for text align on TDs rather than Base's mix_text_align
128
+ # because if possible, using align= rather than a style keeps emails
129
+ # smaller. But for left-aligned text, you gotta use a style because
130
+ # you know, Outlook.
131
+ align = opt[:align]
132
+ unless align.blank?
133
+ td[:align] = align
134
+
135
+ # Must use style to reinforce left-align text in certain email clients.
136
+ # All other alignments are accepted naturally.
137
+ td.style[TEXT_ALIGN] = align if align == LEFT
138
+
139
+ end
140
+
141
+ rowspan = opt[:rowspan].to_i
142
+ td[:rowspan] = rowspan if rowspan > 0
143
+
144
+ mix_font td, opt, ctx, table_opt
145
+
146
+ # In Fluid-Drop, the font-size is set to zero to overcome Outlook rendering
147
+ # problems so it is important to warn the designer that they need to set
148
+ # it back to a reasonable size on the TD element.
149
+ # TODO [JDH 11/14/2015] Decide if the warning re: font-size should ever
150
+ # be restored based on whether or not users are finding it confusing.
151
+
152
+ if mobile.blank?
153
+
154
+ # If the cell doesn't define it's own responsive behavior, check to
155
+ # see if it inherits from its parent table. DROP and SWITCH declared
156
+ # at the table-level descend to their tds.
157
+ pm = table_opt[:mobile]
158
+ mobile = pm if pm == DROP || pm == SWITCH
159
+
160
+ end
161
+
162
+ mix_responsive td, opt, ctx, mobile
163
+
164
+ #outlook-bg <!-&#45;&#91;if gte mso 9]>[n]<v:rect style="width:%width%px;height:%height%px;" strokecolor="none"><v:fill type="tile" src="%src%" /></v:fill></v:rect><v:shape id="theText[rnd]" style="position:absolute;width:%width%px;height:%height%px;margin:0;padding:0;%style%">[n]<!&#91;endif]&#45;->
165
+ #/outlook-bg <!-&#45;&#91;if gte mso 9]></v:shape><!&#91;endif]&#45;->
166
+
167
+ html << td.to_s
168
+
169
+ end
78
170
 
79
- td.to_s
171
+ html
80
172
  end
81
173
 
82
174
  private
@@ -8,7 +8,7 @@ module Inkcite
8
8
 
9
9
  times = []
10
10
 
11
- [ 'source.html', 'source.txt', 'helpers.tsv' ].each do |file|
11
+ ['source.html', 'source.txt', 'helpers.tsv'].each do |file|
12
12
  file = email.project_file(file)
13
13
  times << File.mtime(file).to_i if File.exists?(file)
14
14
  end
@@ -81,8 +81,8 @@ module Inkcite
81
81
  end
82
82
 
83
83
  # TODO: Verify SFTP configuration
84
- host = config[:host]
85
- path = config[:path]
84
+ host = config[:host]
85
+ path = config[:path]
86
86
  username = config[:username]
87
87
  password = config[:password]
88
88
 
@@ -107,7 +107,7 @@ module Inkcite
107
107
  # Upload each version of the email.
108
108
  email.versions.each do |version|
109
109
 
110
- view = email.view(:preview, :browser, version)
110
+ view = email.view(:preview, :email, version)
111
111
 
112
112
  # Need to pass the upload path through the renderer to ensure
113
113
  # that embedded tags will be converted into data.
@@ -117,28 +117,34 @@ module Inkcite
117
117
  # the content and images is present.
118
118
  mkdir! sftp, remote_root
119
119
 
120
+ # Upload the images to the remote directory. We use the last_remote_root
121
+ # to ensure that we're not repeatedly uploading the same images over and
122
+ # over when force is enabled -- but will re-upload images to distinct
123
+ # remote roots.
124
+ copy! sftp, local_images, remote_root, force && last_remote_root != remote_root
125
+ last_remote_root = remote_root
126
+
127
+ # Check to see if we're creating an in-browser version of the email.
128
+ next unless email.formats.include?(:browser)
129
+
130
+ browser_view = email.view(:preview, :browser, version)
131
+
120
132
  # Check to see if there is a HTML version of this preview. Some emails
121
133
  # do not have a hosted version and so it is not necessary to upload the
122
134
  # HTML version of the email - but this is a bad practice.
123
135
  file_name = view.file_name
124
- unless file_name.blank?
125
-
126
- remote_file_name = File.join(remote_root, file_name)
127
- puts "Uploading #{remote_file_name}"
128
-
129
- # We need to use StringIO to write the email to a buffer in order to upload
130
- # the email's content in binary so that its encoding is honored. SFTP defaults
131
- # to ASCII-8bit in non-binary mode, so it was blowing up on UTF-8 special
132
- # characters (e.g. "Mäkinen").
133
- # http://stackoverflow.com/questions/9439289/netsftp-transfer-mode-binary-vs-text
134
- io = StringIO.new(view.render!)
135
- sftp.upload!(io, remote_file_name)
136
-
137
- end
138
-
139
- # Upload the images to the remote directory
140
- copy! sftp, local_images, remote_root, force && last_remote_root != remote_root
141
- last_remote_root = remote_root
136
+ next if file_name.blank?
137
+
138
+ remote_file_name = File.join(remote_root, file_name)
139
+ puts "Uploading #{remote_file_name}"
140
+
141
+ # We need to use StringIO to write the email to a buffer in order to upload
142
+ # the email's content in binary so that its encoding is honored. SFTP defaults
143
+ # to ASCII-8bit in non-binary mode, so it was blowing up on UTF-8 special
144
+ # characters (e.g. "Mäkinen").
145
+ # http://stackoverflow.com/questions/9439289/netsftp-transfer-mode-binary-vs-text
146
+ io = StringIO.new(browser_view.render!)
147
+ sftp.upload!(io, remote_file_name)
142
148
 
143
149
  end
144
150
 
@@ -1,3 +1,3 @@
1
1
  module Inkcite
2
- VERSION = "1.7.0"
2
+ VERSION = "1.8.0"
3
3
  end
data/lib/inkcite/view.rb CHANGED
@@ -61,8 +61,15 @@ module Inkcite
61
61
  @config[FILE_NAME] = file_name
62
62
 
63
63
  # The MediaQuery object manages the responsive styles that are applied to
64
- # the email during rendering.
65
- @media_query = MediaQuery.new(self, 480)
64
+ # the email during rendering. Check to see if a breakwidth has been supplied
65
+ # in helpers.tsv so the designer can control the primary breakpoint.
66
+ breakpoint = @config[:'mobile-breakpoint'].to_i
67
+ if breakpoint <= 0
68
+ breakpoint = @config[:width].to_i - 1
69
+ breakpoint = 480 if breakpoint <= 0
70
+ end
71
+
72
+ @media_query = MediaQuery.new(self, breakpoint)
66
73
 
67
74
  # Set the version index based on the position of this
68
75
  # version in the list of those defined.
@@ -218,7 +225,11 @@ module Inkcite
218
225
  image_host = if development?
219
226
  (@email.optimize_images?? Minifier::IMAGE_CACHE : Email::IMAGES) + '/'
220
227
  else
221
- self[Email::IMAGE_HOST]
228
+
229
+ # Use the image host defined in config.yml or, out-of-the-box refer to images/
230
+ # in the build directory.
231
+ self[Email::IMAGE_HOST] || (Email::IMAGES + '/')
232
+
222
233
  end
223
234
 
224
235
  src_url << image_host unless image_host.blank?
@@ -227,7 +238,7 @@ module Inkcite
227
238
  src_url << src
228
239
 
229
240
  # Cache-bust the image if the caller is expecting it to be there.
230
- src_url << "?#{Time.now.to_i}" if is_enabled?(Email::CACHE_BUST)
241
+ src_url << "?#{Time.now.to_i}" if !production? && is_enabled?(Email::CACHE_BUST)
231
242
 
232
243
  # Transpose any embedded tags into actual values.
233
244
  Renderer.render(src_url, self)
@@ -361,7 +372,15 @@ module Inkcite
361
372
  html << '<meta name="viewport" content="width=device-width"/>'
362
373
  html << "<meta name=\"generator\" content=\"Inkcite #{Inkcite::VERSION}\"/>"
363
374
 
364
- html << "<title>#{self.title}</title>"
375
+ # Enable responsive media queries on Windows phones courtesy of @jamesmacwhite
376
+ # https://blog.jmwhite.co.uk/2014/03/01/windows-phone-does-support-css3-media-queries-in-html-email/
377
+ html << '<!--[if !mso]><!-->'
378
+ html << '<meta http-equiv="X-UA-Compatible" content="IE=edge" />'
379
+ html << '<!--<![endif]-->'
380
+
381
+ # Some native Android clients display the title before the preheader so
382
+ # don't include it in non-development or email rendering per @moonstrips
383
+ html << "<title>#{self.title if (development? || browser?)}</title>"
365
384
 
366
385
  # Add external script sources.
367
386
  html += external_scripts
@@ -79,4 +79,8 @@ describe Inkcite::Renderer::Image do
79
79
  @view.media_query.find_by_klass('i01').to_css.must_equal('img[class~="i01"] { content: url("images/inkcite-mobile.jpg") !important; }')
80
80
  end
81
81
 
82
+ it 'supports fluid-hybrid desktop and style' do
83
+ Inkcite::Renderer.render('{img src=inkcite.jpg height=200 width=325 mobile=fluid}', @view).must_equal('<img border=0 src="images/inkcite.jpg" style="display:block;height:auto;max-width:325px;width:100%" width=325>')
84
+ end
85
+
82
86
  end
@@ -31,4 +31,24 @@ describe Inkcite::Renderer::Table do
31
31
  Inkcite::Renderer.render('{table margin=15 margin-left=8}', @view).must_equal('<table border=0 cellpadding=0 cellspacing=0 style="margin-bottom:15px;margin-left:8px;margin-right:15px;margin-top:15px"><tr>')
32
32
  end
33
33
 
34
+ it 'supports fluid-hybrid desktop and style' do
35
+ Inkcite::Renderer.render('{table width=500 mobile=fluid}', @view).must_equal(%Q(<!--[if mso]><table border=0 cellpadding=0 cellspacing=0 width=500><tr><td><![endif]--><table border=0 cellpadding=0 cellspacing=0 style="max-width:500px" width=100%><tr>))
36
+ end
37
+
38
+ it 'carries table alignment into the Outlook wrap table in fluid-hybrid' do
39
+ Inkcite::Renderer.render('{table align=center width=500 mobile=fluid}', @view).must_equal(%Q(<!--[if mso]><table align=center border=0 cellpadding=0 cellspacing=0 width=500><tr><td><![endif]--><table align=center border=0 cellpadding=0 cellspacing=0 style="max-width:500px" width=100%><tr>))
40
+ end
41
+
42
+ it 'supports fluid-drop desktop and style' do
43
+
44
+ markup = ''
45
+ markup << %Q({table font-size=25 bgcolor=#090 border="5px solid #f0f" padding=15 width=600 mobile="fluid-drop"})
46
+ markup << %Q({td width=195 bgcolor=#009 color=#fff valign=top}left{/td})
47
+ markup << %Q({td width=195 align=center}centered two-line{/td})
48
+ markup << %Q({td width=195 bgcolor=#900 color=#fff align=right font-size=30}right<br>three<br>lines{/td})
49
+ markup << %Q({/table})
50
+
51
+ Inkcite::Renderer.render(markup, @view).must_equal(%Q(<!--[if mso]><table border=0 cellpadding=0 cellspacing=0 width=600><tr><td><![endif]--><table bgcolor=#009900 border=0 cellpadding=0 cellspacing=0 style="border:5px solid #f0f;max-width:600px" width=100%><tr><td style="font-size:0;text-align:center;vertical-align:middle"><!--[if mso]><table align=center border=0 cellpadding=0 cellspacing=0 width=100%><tr><![endif]--><!--[if mso]><td valign=top width=195><![endif]--><div class="fill" style="display:inline-block;vertical-align:top;width:195px"><table border=0 cellpadding=15 cellspacing=0 width=100%><tr><td align=left bgcolor=#000099 style="color:#ffffff;font-size:25px;padding:15px;text-align:left" valign=top>left</td></tr></table></div><!--[if mso]></td><![endif]--><!--[if mso]><td valign=middle width=195><![endif]--><div class="fill" style="display:inline-block;vertical-align:middle;width:195px"><table border=0 cellpadding=15 cellspacing=0 width=100%><tr><td align=center style="font-size:25px;padding:15px" valign=middle>centered two-line</td></tr></table></div><!--[if mso]></td><![endif]--><!--[if mso]><td valign=middle width=195><![endif]--><div class="fill" style="display:inline-block;vertical-align:middle;width:195px"><table border=0 cellpadding=15 cellspacing=0 width=100%><tr><td align=right bgcolor=#990000 style="color:#ffffff;font-size:30px;padding:15px" valign=middle>right<br>three<br>lines</td></tr></table></div><!--[if mso]></td><![endif]--><!--[if mso]></tr></table><![endif]--></td></tr></table><!--[if mso]></td></tr></table><![endif]-->))
52
+ end
53
+
34
54
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inkcite
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.0
4
+ version: 1.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeffrey D. Hoffman
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-11-04 00:00:00.000000000 Z
11
+ date: 2015-11-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -94,6 +94,20 @@ dependencies:
94
94
  - - '>='
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: htmlbeautifier
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
97
111
  - !ruby/object:Gem::Dependency
98
112
  name: image_optim
99
113
  requirement: !ruby/object:Gem::Requirement
@@ -313,9 +327,6 @@ files:
313
327
  - assets/init/image_optim.yml
314
328
  - assets/init/source.html
315
329
  - bin/inkcite
316
- - bin/release-major
317
- - bin/release-minor
318
- - bin/release-patch
319
330
  - inkcite.gemspec
320
331
  - lib/inkcite.rb
321
332
  - lib/inkcite/cli/base.rb
data/bin/release-major DELETED
@@ -1 +0,0 @@
1
- gem bump --version major --push --release
data/bin/release-minor DELETED
@@ -1 +0,0 @@
1
- gem bump --version minor --push --release
data/bin/release-patch DELETED
File without changes