inkcite 1.12.1 → 1.13.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.
@@ -11,8 +11,11 @@ module Inkcite
11
11
  BACKGROUND_SIZE = :'background-size'
12
12
  BORDER_BOTTOM = :'border-bottom'
13
13
  BORDER_COLLAPSE = :'border-collapse'
14
+ BORDER_LEFT = :'border-left'
14
15
  BORDER_RADIUS = :'border-radius'
16
+ BORDER_RIGHT = :'border-right'
15
17
  BORDER_SPACING = :'border-spacing'
18
+ BORDER_TOP = :'border-top'
16
19
  BOX_SHADOW = :'box-shadow'
17
20
  FONT_FAMILY = :'font-family'
18
21
  FONT_SIZE = :'font-size'
@@ -37,11 +40,8 @@ module Inkcite
37
40
  WEBKIT_ANIMATION = :'-webkit-animation'
38
41
  WHITE_SPACE = :'white-space'
39
42
 
40
- # CSS Margins
41
- MARGINS = [MARGIN_TOP, MARGIN_LEFT, MARGIN_BOTTOM, MARGIN_RIGHT]
42
-
43
- # CSS Directions
44
- DIRECTIONS = [:top, :right, :bottom, :left]
43
+ # CSS direction suffixes including nil/empty for convenience.
44
+ DIRECTIONS = [ nil, :top, :right, :bottom, :left]
45
45
 
46
46
  # Attribute and CSS dimensions
47
47
  DIMENSIONS = [:width, :height]
@@ -85,6 +85,11 @@ module Inkcite
85
85
  none?(bgcolor) ? nil : hex(bgcolor)
86
86
  end
87
87
 
88
+ def detect_bggradient opt, default=nil
89
+ bggradient = detect(opt[:gradient], opt[:bggradient], opt[BACKGROUND_GRADIENT])
90
+ none?(bggradient) ? nil : hex(bggradient)
91
+ end
92
+
88
93
  # Convenience pass-thru to Renderer's static helper method.
89
94
  def hex color
90
95
  Renderer.hex(color)
@@ -118,14 +123,13 @@ module Inkcite
118
123
  element.style[BACKGROUND_COLOR] = bgcolor if bgcolor
119
124
 
120
125
  # Background gradient support
121
- bggradient = detect(opt[:bggradient] || opt[BACKGROUND_GRADIENT])
126
+ bggradient = detect_bggradient(opt)
122
127
  unless none?(bggradient)
123
128
 
124
129
  # As a shortcut a gradient can be specified simply by designating
125
130
  # both a bgcolor and the gradient color - this will insert a radial
126
131
  # gradient automatically.
127
132
  if bggradient.start_with?('#')
128
- bggradient = hex(bggradient)
129
133
 
130
134
  # If a bgcolor is provided, the gradient goes bgcolor -> bggradient.
131
135
  # Otherwise, it goes bggradient->darker(bggradient)
@@ -141,18 +145,7 @@ module Inkcite
141
145
  end
142
146
 
143
147
  def mix_border element, opt, ctx
144
-
145
- border = opt[:border]
146
- element.style[:border] = border unless border.blank?
147
-
148
- # Iterate through each of the possible borders and apply them individually
149
- # to the style if they are defined.
150
- DIRECTIONS.each do |dir|
151
- key = :"border-#{dir}"
152
- border = opt[key]
153
- element.style[key] = border unless border.blank? || border == NONE
154
- end
155
-
148
+ mix_directional element, element.style, opt, ctx, :border
156
149
  end
157
150
 
158
151
  def mix_border_radius element, opt, ctx
@@ -162,6 +155,27 @@ module Inkcite
162
155
 
163
156
  end
164
157
 
158
+ # Helper to mix CSS properties that can be defined as either a
159
+ # singular shorthand (e.g. border) or in one or more of the
160
+ # compass directions (e.g. border-top, border-left).
161
+ def mix_directional element, into, opt, ctx, opt_key, css_key=nil, as_px=false
162
+
163
+ css_key = opt_key if css_key.nil?
164
+
165
+ # Iterate through each of the possible directions (including blank)
166
+ # and apply them each to the element's style hash.
167
+ DIRECTIONS.each do |dir|
168
+ dir_opt_key = add_directional_suffix(opt_key, dir)
169
+ dir_css_key = add_directional_suffix(css_key, dir)
170
+ value = opt[dir_opt_key]
171
+ next if none?(value)
172
+
173
+ value = px(value) if as_px
174
+ into[dir_css_key] = value
175
+ end
176
+
177
+ end
178
+
165
179
  def mix_font element, opt, ctx, parent=nil
166
180
 
167
181
  # Always ensure we have a parent to inherit from.
@@ -201,19 +215,16 @@ module Inkcite
201
215
 
202
216
  def mix_margins element, opt, ctx
203
217
 
204
- # Check to see if the 'all' margin attribute is specified.
205
- all_margin = opt[MARGIN]
218
+ # Outlook supports Margin, not margin.
219
+ mix_directional element, element.style, opt, ctx, :margin, :Margin, true
206
220
 
207
- # Applying individual margins helps ensure compatibility across
208
- # all email clients. Cough! Cough! Looking at you Outlook.
209
- MARGINS.each do |margin|
221
+ end
210
222
 
211
- # Check for specific direction margin (e.g. margin-left) which
212
- # would override all margins. Otherwise, inherit all margin.
213
- amt = (opt[margin] || all_margin).to_i
214
- element.style[margin] = px(amt) if amt > 0
223
+ # Text alignment - left, right, center.
224
+ def mix_text_align element, opt, ctx
215
225
 
216
- end
226
+ align = detect(opt[:align] || opt[TEXT_ALIGN])
227
+ element.style[TEXT_ALIGN] = align unless none?(align)
217
228
 
218
229
  end
219
230
 
@@ -280,6 +291,24 @@ module Inkcite
280
291
  html
281
292
  end
282
293
 
294
+ private
295
+
296
+ # Helper method which adds the directional suffix (e.g. top)
297
+ # to the provided key (border) and converts to a symbol.
298
+ def add_directional_suffix key, dir
299
+
300
+ # Nothing to do if the direction isn't provided.
301
+ return key if dir.blank?
302
+
303
+ # Need to convert the key to a string since it is likely
304
+ # a symbol that has been provided.
305
+ dir_key = ''
306
+ dir_key << key.to_s
307
+ dir_key << '-'
308
+ dir_key << dir.to_s
309
+ dir_key.to_sym
310
+ end
311
+
283
312
  end
284
313
  end
285
314
  end
@@ -11,12 +11,17 @@ module Inkcite
11
11
  mix_border element, opt, ctx
12
12
  mix_border_radius element, opt, ctx
13
13
  mix_font element, opt, ctx
14
+ mix_margins element, opt, ctx
14
15
  mix_text_align element, opt, ctx
15
16
 
16
17
  # Supports both integers and mixed padding (e.g. 10px 20px)
17
18
  padding = opt[:padding]
18
19
  element.style[:padding] = px(padding) unless none?(padding)
19
20
 
21
+ # Supports custom padding on mobile - e.g. mobile-padding="15px 5px"
22
+ mobile_padding = opt[MOBILE_PADDING]
23
+ element.mobile_style[:padding] = px(mobile_padding) unless none?(mobile_padding)
24
+
20
25
  # Vertical alignment - top, middle, bottom.
21
26
  valign = opt[:valign]
22
27
  element.style[VERTICAL_ALIGN] = valign unless none?(valign)
@@ -33,11 +38,13 @@ module Inkcite
33
38
  element.to_s
34
39
  end
35
40
 
36
- # Text alignment - left, right, center.
37
- def mix_text_align element, opt, ctx
41
+ def mix_width element, opt, ctx
42
+
43
+ width = opt[:width]
44
+ element.style[:width] = px(width) unless width.blank?
38
45
 
39
- align = opt[:align]
40
- element.style[TEXT_ALIGN] = align unless none?(align)
46
+ mobile_width = opt[MOBILE_WIDTH]
47
+ element.mobile_style[:width] = px(mobile_width) unless mobile_width.blank?
41
48
 
42
49
  end
43
50
 
@@ -8,8 +8,7 @@ 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?
11
+ mix_width div, opt, ctx
13
12
 
14
13
  height = opt[:height].to_i
15
14
  div.style[:height] = px(height) if height > 0
@@ -2,7 +2,7 @@ module Inkcite
2
2
  module Renderer
3
3
  class Element
4
4
 
5
- attr_reader :tag
5
+ attr_reader :mobile_style, :style, :tag
6
6
 
7
7
  def initialize tag, att={}
8
8
 
@@ -19,7 +19,10 @@ module Inkcite
19
19
 
20
20
  # For caller convenience, accept a style hash from the attributes
21
21
  # or initialize it here.
22
- @styles = att.delete(:style) || {}
22
+ @style = att.delete(:style) || {}
23
+
24
+ # Collection of mobile-only CSS properties for this element.
25
+ @mobile_style = att.delete(:mobile_style) || {}
23
26
 
24
27
  end
25
28
 
@@ -67,10 +70,6 @@ module Inkcite
67
70
  @self_close
68
71
  end
69
72
 
70
- def style
71
- @styles
72
- end
73
-
74
73
  # Generates a Helper tag rather than a string tag - e.g. {img src=test.png}
75
74
  # rather than <img src=test.png>
76
75
  def to_helper
@@ -80,7 +79,7 @@ module Inkcite
80
79
  def to_s open='<', close='>'
81
80
 
82
81
  # Convert the style hash into CSS style attribute.
83
- @att[:style] = Renderer.quote(Renderer.render_styles(@styles)) unless @styles.blank?
82
+ @att[:style] = Renderer.quote(Renderer.render_styles(@style)) unless @style.blank?
84
83
 
85
84
  # Convert the list of CSS classes assigned to this element into an attribute
86
85
  self[:class] = Renderer.quote(@classes.to_a.sort.join(' ')) unless @classes.blank?
@@ -2,29 +2,34 @@ module Inkcite
2
2
  module Renderer
3
3
  class Responsive < Base
4
4
 
5
- BUTTON = 'button'
6
- DROP = 'drop'
7
- FILL = 'fill'
8
- FLUID = 'fluid'
9
- FLUID_DROP = 'fluid-drop'
5
+ BUTTON = 'button'
6
+ DROP = 'drop'
7
+ FILL = 'fill'
8
+ FLUID = 'fluid'
9
+ FLUID_DROP = 'fluid-drop'
10
10
  FLUID_STACK = 'fluid-stack'
11
- HIDE = 'hide'
12
- IMAGE = 'img'
13
- SHOW = 'show'
11
+ HIDE = 'hide'
12
+ IMAGE = 'img'
13
+ SHOW = 'show'
14
14
  SHOW_INLINE = 'show-inline'
15
- SWITCH = 'switch'
16
- SWITCH_UP = 'switch-up'
17
- TOGGLE = 'toggle'
15
+ SWITCH = 'switch'
16
+ SWITCH_UP = 'switch-up'
17
+ TOGGLE = 'toggle'
18
18
 
19
19
  # For elements that take on different background properties
20
20
  # when they go responsive
21
- MOBILE_BGCOLOR = :'mobile-bgcolor'
22
- MOBILE_BACKGROUND = :'mobile-background'
23
- MOBILE_BACKGROUND_COLOR = :'mobile-background-color'
24
- MOBILE_BACKGROUND_IMAGE = :'mobile-background-image'
25
- MOBILE_BACKGROUND_REPEAT = :'mobile-background-repeat'
21
+ MOBILE_BGCOLOR = :'mobile-bgcolor'
22
+ MOBILE_BACKGROUND = :'mobile-background'
23
+ MOBILE_BACKGROUND_COLOR = :'mobile-background-color'
24
+ MOBILE_BACKGROUND_IMAGE = :'mobile-background-image'
25
+ MOBILE_BACKGROUND_REPEAT = :'mobile-background-repeat'
26
26
  MOBILE_BACKGROUND_POSITION = :'mobile-background-position'
27
- MOBILE_BACKGROUND_SIZE = :'mobile-background-size'
27
+ MOBILE_BACKGROUND_SIZE = :'mobile-background-size'
28
+ MOBILE_SRC = :'mobile-src'
29
+
30
+ # Other mobile-specific properties
31
+ MOBILE_PADDING = :'mobile-padding'
32
+
28
33
 
29
34
  class Rule
30
35
 
@@ -58,7 +63,7 @@ module Inkcite
58
63
  end
59
64
 
60
65
  def att_selector_string
61
- "[class~=#{Renderer.quote(@klass)}]"
66
+ ".#{@klass}"
62
67
  end
63
68
 
64
69
  def block?
@@ -153,7 +158,7 @@ module Inkcite
153
158
  # FILL causes specific types of elements to expand to 100% of the available
154
159
  # width of the mobile device.
155
160
  styles << Rule.new('img', FILL, 'width: 100% !important; height: auto !important;', false)
156
- styles << Rule.new([ 'table', 'td' ], FILL, 'width: 100% !important; background-size: 100% auto !important;', false)
161
+ styles << Rule.new(['table', 'td'], FILL, 'width: 100% !important; background-size: 100% auto !important;', false)
157
162
 
158
163
  # For mobile-image tags.
159
164
  styles << Rule.new('span', IMAGE, 'display: block; background-position: center; background-size: cover;', false)
@@ -198,6 +203,11 @@ module Inkcite
198
203
  mobile == FLUID_DROP || mobile == FLUID_STACK
199
204
  end
200
205
 
206
+ def mix_border element, opt, ctx
207
+ super
208
+ mix_directional element, element.mobile_style, opt, ctx, MOBILE_BORDER, :border
209
+ end
210
+
201
211
  def mix_font element, opt, ctx, parent=nil
202
212
 
203
213
  # Let the super class do its thing and grab the name of the font
@@ -205,29 +215,39 @@ module Inkcite
205
215
  font = super
206
216
 
207
217
  # Will hold the mobile font overrides for this element, if any.
208
- style = { }
218
+ font_family = detect_font(MOBILE_FONT_FAMILY, font, opt, parent, ctx)
219
+ element.mobile_style[FONT_FAMILY] = font_family unless font_family.blank?
209
220
 
210
221
  font_size = detect_font(MOBILE_FONT_SIZE, font, opt, parent, ctx)
211
- style[FONT_SIZE] = "#{px(font_size)} !important" unless font_size.blank?
212
-
213
- line_height = detect_font(MOBILE_LINE_HEIGHT, font, opt, parent, ctx)
214
- style[LINE_HEIGHT] = "#{px(line_height)} !important" unless line_height.blank?
222
+ element.mobile_style[FONT_SIZE] = px(font_size) unless font_size.blank?
215
223
 
216
224
  color = detect_font(MOBILE_FONT_COLOR, font, opt, parent, ctx)
217
- style[:color] = "#{hex(color)} !important" unless color.blank?
225
+ element.mobile_style[:color] = hex(color) unless color.blank?
226
+
227
+ font_weight = detect_font(MOBILE_FONT_WEIGHT, font, opt, parent, ctx)
228
+ element.mobile_style[FONT_WEIGHT] = font_weight unless font_weight.blank?
218
229
 
219
- mix_responsive_style element, opt, ctx, Renderer.render_styles(style) unless style.blank?
230
+ letter_spacing = detect_font(MOBILE_LETTER_SPACING, font, opt, parent, ctx)
231
+ element.mobile_style[LETTER_SPACING] = px(letter_spacing) unless none?(letter_spacing)
232
+
233
+ line_height = detect_font(MOBILE_LINE_HEIGHT, font, opt, parent, ctx)
234
+ element.mobile_style[LINE_HEIGHT] = px(line_height) unless line_height.blank?
220
235
 
221
236
  font
222
237
  end
223
238
 
239
+ def mix_margins element, opt, ctx
240
+ super
241
+ mix_directional element, element.mobile_style, opt, ctx, MOBILE_MARGIN, :margin, true
242
+ end
243
+
224
244
  def mix_responsive element, opt, ctx, klass=nil
225
245
 
226
246
  # Apply the "mobile" attribute or use the override if one was provided.
227
247
  mix_responsive_klass element, opt, ctx, klass || opt[:mobile]
228
248
 
229
249
  # Apply the "mobile-style" attribute if one was provided.
230
- mix_responsive_style element, opt, ctx, opt[MOBILE_STYLE]
250
+ mix_responsive_style element, opt, ctx
231
251
 
232
252
  end
233
253
 
@@ -293,11 +313,54 @@ module Inkcite
293
313
 
294
314
  end
295
315
 
296
- def mix_responsive_style element, opt, ctx, declarations=nil
316
+ def mix_responsive_style element, opt, ctx
317
+
318
+ # Warn that mobile-style is no longer supported. Developers should
319
+ # use the stronger, faster, better-er mobile-* attributes
320
+ __unsupported_style = element[MOBILE_STYLE]
321
+ ctx.errors('The mobile-style attribute is no longer supported', { :element => element.to_s, :mobile_style => __unsupported_style }) unless __unsupported_style.blank?
322
+
323
+ _mobile_style = element.mobile_style
324
+ return if _mobile_style.blank?
325
+
326
+ # Will hold a preprocessed list of direction-free, lowercased properties
327
+ # (ahem, Outlook Margin) so we can easily determine if a mobile style
328
+ # needs !important to override its desktop style value.
329
+ desktop_style_keys = Set.new
330
+ desktop_style = {}
331
+ element.style.each_pair do |key, css|
297
332
 
298
- # Check to see if a mobile style (e.g. "mobile-style='background-color: #ff0;'")
299
- # has been declared for this element.
300
- declarations ||= opt[MOBILE_STYLE]
333
+ key = key.to_s.downcase
334
+ desktop_style[key.to_sym] = css
335
+
336
+ base_key = get_directionless_key(key)
337
+ desktop_style_keys.add(base_key)
338
+
339
+ end
340
+
341
+ # This will hold the decorated list of CSS properties. If the element
342
+ # has any existing styles that are being overridden in the mobile styles
343
+ # we need to append the !important flag.
344
+ decorated_style = {}
345
+
346
+ # Iterate through the defined mobile styles, determine which need to
347
+ # have !important and assemble a new hash to be rendered as CSS.
348
+ _mobile_style.each_pair do |key, css|
349
+
350
+ # No need to put attributes in the mobile style if they match
351
+ # the existing desktop style of the element.
352
+ next if css == desktop_style[key]
353
+
354
+ # Append !important to the CSS if it overrides a value in the
355
+ # element's in-lined styles. Need to test bo
356
+ base_key = get_directionless_key(key)
357
+ css = "#{css} !important" if desktop_style_keys.include?(base_key)
358
+
359
+ decorated_style[key] = css
360
+ end
361
+
362
+ # Render the array of styles to a CSS declaration string
363
+ declarations = Renderer.render_styles decorated_style
301
364
  return if declarations.blank?
302
365
 
303
366
  mq = ctx.media_query
@@ -307,11 +370,11 @@ module Inkcite
307
370
  # If no klass was specified, check to see if any previously defined rule matches
308
371
  # the style declarations. If so, we'll reuse that rule and apply the klass
309
372
  # to this object to avoid unnecessary duplication in the HTML.
310
- rule = mq.find_by_declaration(declarations);
373
+ rule = mq.find_by_declaration(declarations)
311
374
  if rule.nil?
312
375
 
313
376
  # Generate a unique class name for this style if it has not already been declared.
314
- # These are of the form m001, etc. Redability is not important because it's
377
+ # These are of the form m001, etc. Readability is not important because it's
315
378
  # dynamically generated and referenced.
316
379
  klass = unique_klass(ctx)
317
380
 
@@ -336,22 +399,46 @@ module Inkcite
336
399
  end
337
400
 
338
401
  def unique_klass ctx
339
- "m%1d" % ctx.unique_id(:m)
402
+ 'm%1d' % ctx.unique_id(:m)
340
403
  end
341
404
 
342
405
  private
343
406
 
344
407
  # Attribute used to declare custom mobile styles for an element.
408
+ MOBILE_BORDER = :'mobile-border'
409
+ MOBILE_MARGIN = :'mobile-margin'
345
410
  MOBILE_STYLE = :'mobile-style'
411
+ MOBILE_WIDTH = :'mobile-width'
346
412
 
347
413
  # Universal CSS selector.
348
414
  UNIVERSAL = '*'
349
415
 
350
- # For font overrides on mobile devices.
351
- MOBILE_FONT_COLOR = :'mobile-color'
352
- MOBILE_FONT_SIZE = :'mobile-font-size'
416
+ # For font overrides on mobile devices. These values are read from
417
+ # the object's attributes and installed into the element's mobile_styles.
418
+ MOBILE_FONT_COLOR = :'mobile-color'
419
+ MOBILE_FONT_FAMILY = :'mobile-font-family'
420
+ MOBILE_FONT_SIZE = :'mobile-font-size'
421
+ MOBILE_FONT_WEIGHT = :'mobile-font-weight'
422
+ MOBILE_LETTER_SPACING = :'mobile-letter-spacing'
353
423
  MOBILE_LINE_HEIGHT = :'mobile-line-height'
354
424
 
425
+ # Accepts a key, such as border or border-left, and returns the
426
+ # key sans direction suffix - so border and border respectively.
427
+ # The opposite of add_directional_suffix().
428
+ def get_directionless_key key
429
+ key = key.to_s
430
+
431
+ # Iterate through the possible directions and if the key ends
432
+ # with the separator and direction (e.g. -left) trim that off
433
+ # and return it.
434
+ DIRECTIONS.each do |dir|
435
+ dir = "-#{dir}"
436
+ return key[0, dir.length] if key.end_with?(dir)
437
+ end
438
+
439
+ key
440
+ end
441
+
355
442
  end
356
443
  end
357
444
  end