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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 729a1ea12dcb8c0180541d0b4aebb22346985f2f
4
- data.tar.gz: c10280cade618463c9ecebe81e50b1acca46a49e
3
+ metadata.gz: db3a1196fe9bcc49e718925d27731a469807acdc
4
+ data.tar.gz: 4dfc1aa8998ec37e5ed03a81facaf7f75ac6bfc9
5
5
  SHA512:
6
- metadata.gz: 39f5ebde420c845be3b262d7b4266a491bed516f561a5679616bd4d235693adbba3c6990bdc9b51c61facb00dcfd35b418c2c2f7f3602f13f7daeb7de971bdf6
7
- data.tar.gz: 2f8441f0f65e4faf1323229c30a8fbdd2ea4b31fc8cdc61f4a1520921bc23fb3cb6727e76cd898b104227f047a924d1af7150180dd8e152fa6e36d14c5ae586a
6
+ metadata.gz: e1e085b9a0668c30f9c6bd968f962caa42205b45702c4c9cba5893bb71ea1fe0b63f6e85c90e767912c42640f36f3a94ed11b54078d189323fc1886a5d37ab7c
7
+ data.tar.gz: 92166f271e19bbcc7058136e2d77a441179a341ef8107d19c24a0f15f8ddc883c319d5410a50edfecfdac89180b1d518dd5270a1614fd09846f8f3145c3d08c6
@@ -1,135 +1,157 @@
1
1
  module Inkcite
2
- module Animation
2
+ class Animation
3
3
 
4
4
  class Keyframe
5
5
 
6
- attr_reader :percent
6
+ attr_reader :percent, :style
7
+
8
+ def initialize percent, ctx, styles={}
7
9
 
8
- def initialize percent, styles={}
9
10
  # Animation percents are always rounded to the nearest whole number.
10
11
  @percent = percent.round(0)
11
- @styles = styles
12
+
13
+ # Instantiate a new Style for this percentage.
14
+ @style = Inkcite::Renderer::Style.new("#{@percent}%", ctx, styles)
15
+
12
16
  end
13
17
 
14
18
  def [] key
15
- @styles[key]
19
+ @style[key]
16
20
  end
17
21
 
18
22
  def []= key, val
19
- @styles[key] = val
23
+ @style[key] = val
20
24
  end
21
25
 
26
+ # For style chaining - e.g. keyframe.add(:key1, 'val').add(:key)
22
27
  def add key, val
23
- self[key] = val
28
+ @style[key] = val
24
29
  self
25
30
  end
26
31
 
32
+ # Appends a value to an existing key
33
+ def append key, val
34
+
35
+ @style[key] ||= ''
36
+ @style[key] << ' ' unless @style[key].blank?
37
+ @style[key] << val
38
+
39
+ end
40
+
27
41
  def add_with_prefixes key, val, ctx
28
42
 
29
- Animation.get_prefixes(ctx).each do |prefix|
43
+ ctx.prefixes.each do |prefix|
30
44
  _key = "#{prefix}#{key}".to_sym
31
45
  self[_key] = val
32
46
  end
47
+
33
48
  self
34
49
  end
35
50
 
36
- def to_s
51
+ def to_css prefix
52
+ @style.to_css(prefix)
53
+ end
37
54
 
38
- css = ''
39
- css << " #{@percent}%"
40
- css << ' ' * (7 - css.length)
41
- css << '{ '
42
- css << Renderer.render_styles(@styles)
43
- css << ' }'
55
+ private
44
56
 
45
- css
46
- end
57
+ # Creates a copy of the array of styles with the appropriate
58
+ # properties (e.g. transform) prefixed.
59
+ def get_prefixed_styles prefix
47
60
 
48
- end
61
+ _styles = {}
49
62
 
50
- class Keyframes
63
+ @styles.each_pair do |key, val|
64
+ key = "#{prefix}#{key}".to_sym if Inkcite::Renderer::Style.needs_prefixing?(key)
65
+ _styles[key] = val
66
+ end
51
67
 
52
- def initialize name, context
53
- @name = name
54
- @ctx = context
55
- @keyframes = []
68
+ _styles
56
69
  end
57
70
 
58
- def << keyframe
59
- @keyframes << keyframe
60
- end
71
+ end
61
72
 
62
- def add_keyframe percent, styles
63
- self << Keyframe.new(percent, styles)
64
- end
73
+ # Infinite iteration count
74
+ INFINITE = 'infinite'
65
75
 
66
- def to_s
76
+ # Timing functions
77
+ LINEAR = 'linear'
78
+ EASE = 'ease'
79
+ EASE_IN_OUT = 'ease-in-out'
67
80
 
68
- css = ''
81
+ # Animation name, view context and array of keyframes
82
+ attr_reader :name, :ctx
69
83
 
70
- keyframe_css = @keyframes.sort { |kf1,kf2| kf1.percent <=> kf2.percent }.collect(&:to_s).join("\n")
84
+ attr_accessor :duration, :timing_function, :delay, :iteration_count
71
85
 
72
- prefixes = Animation.get_prefixes(@ctx)
86
+ def initialize name, ctx
87
+ @name = name
88
+ @ctx = ctx
73
89
 
74
- prefixes.each do |prefix|
75
- css << "@#{prefix}keyframes #{@name} {\n"
76
- css << keyframe_css
77
- css << "\n}\n"
78
- end
90
+ # Default values for the animation's properties
91
+ @duration = 1
92
+ @delay = 0
93
+ @iteration_count = INFINITE
94
+ @timing_function = LINEAR
79
95
 
80
- css
81
- end
96
+ # Initialize the keyframes
97
+ @keyframes = []
82
98
 
83
99
  end
84
100
 
85
- def self.get_prefixes ctx
86
- ALL_BROWSERS
87
- end
101
+ def add_keyframe percent, styles={}
102
+ keyframe = Keyframe.new(percent, @ctx, styles)
88
103
 
89
- # True if we're limiting the animation to webkit only. In development
90
- # or in the browser version of the email, the animation should be as
91
- # compatible as possible but in all other cases it should be webkit only.
92
- def self.webkit_only? ctx
93
- false #&& !(ctx.development? || ctx.browser?)
104
+ @keyframes << keyframe
105
+
106
+ keyframe
94
107
  end
95
108
 
96
- # Renders the CSS with the appropriate browser prefixes based
97
- # on whether or not this version of the email is webkit only.
98
- def self.with_browser_prefixes css, ctx, opts={}
109
+ def to_keyframe_css
110
+
111
+ css = ''
112
+
113
+ # Sort the keyframes by percent in ascending order.
114
+ sorted_keyframes = @keyframes.sort { |kf1, kf2| kf1.percent <=> kf2.percent }
99
115
 
100
- indentation = opts[:indentation] || ''
116
+ # Iterate through each prefix and render a set of keyframes
117
+ # for each.
118
+ @ctx.prefixes.each do |prefix|
119
+ css << "@#{prefix}keyframes #{@name} {\n"
120
+ css << sorted_keyframes.collect { |kf| kf.to_css(prefix) }.join("\n")
121
+ css << "\n}\n"
122
+ end
123
+
124
+ css
101
125
 
102
- # Convert an integer indentation value into that number of spaces.
103
- indentation = ' ' * indentation if indentation.is_a?(Integer)
126
+ end
104
127
 
105
- separator = opts[:separator] || "\n"
128
+ # Renders this Animation declaration in the syntax defined here
129
+ # https://developer.mozilla.org/en-US/docs/Web/CSS/animation
130
+ # e.g. "3s ease-in 1s 2 reverse both paused slidein"
131
+ def to_s
106
132
 
107
- # Determine which prefixes will be applied.
108
- browser_prefixes = webkit_only?(ctx) ? WEBKIT_BROWSERS : ALL_BROWSERS
133
+ # The desired format is: duration | timing-function | delay |
134
+ # iteration-count | direction | fill-mode | play-state | name
135
+ # Although currently not all attributes are supported.
136
+ css = [
137
+ seconds(@duration),
138
+ @timing_function
139
+ ]
109
140
 
110
- # This will hold the completed CSS with all prefixes applied.
111
- _css = ''
141
+ css << seconds(@delay) if @delay > 0
112
142
 
113
- # Iterate through the prefixes and apply them with the indentation
114
- # and CSS declaration with line breaks.
115
- browser_prefixes.each do |prefix|
116
- _css << indentation
117
- _css << prefix
118
- _css << css
119
- _css << separator
120
- end
143
+ css << @iteration_count
121
144
 
122
- _css
145
+ css << @name
146
+
147
+ css.join(' ')
123
148
  end
124
149
 
125
150
  private
126
151
 
127
-
128
- # Static arrays with browser prefixes. Turns out that Firefox, IE and Opera
129
- # don't require a prefix so to target everything we need the non-prefixed version
130
- # (hence the blank entry) plus the webkit prefix.
131
- WEBKIT_BROWSERS = ['-webkit-']
132
- ALL_BROWSERS = [''] + WEBKIT_BROWSERS
152
+ def seconds val
153
+ "#{val}s"
154
+ end
133
155
 
134
156
  end
135
157
  end
@@ -49,6 +49,9 @@ module Inkcite
49
49
  :aliases => '-a',
50
50
  :desc => 'Add one or more (space-separated) recipients to this specific mailing',
51
51
  :type => :array
52
+ option :'no-upload',
53
+ :desc => 'Skip the asset upload, email the preview immediately',
54
+ :type => :boolean
52
55
  def preview to=:developer
53
56
  require_relative 'preview'
54
57
  Cli::Preview.invoke(email, to, options)
@@ -100,6 +103,9 @@ module Inkcite
100
103
  end
101
104
 
102
105
  desc 'test [options]', 'Tests (or re-tests) the email with Litmus or Email on Acid'
106
+ option :'no-upload',
107
+ :desc => 'Skip the asset upload, test the email immediately',
108
+ :type => :boolean
103
109
  option :version,
104
110
  :aliases => '-v',
105
111
  :desc => 'Test a specific version of the email'
@@ -8,7 +8,7 @@ module Inkcite
8
8
 
9
9
  # Push the browser preview(s) up to the server to ensure that the
10
10
  # latest images and "view in browser" versions are available.
11
- email.upload
11
+ email.upload unless opt[:'no-upload']
12
12
 
13
13
  also = opt[:also]
14
14
  unless also.blank?
@@ -29,9 +29,9 @@ module Inkcite
29
29
  USAGE
30
30
  end
31
31
 
32
- # Push the browser preview up to the server to ensure that the
33
- # latest images are available.
34
- email.upload
32
+ # Unless disabled, push the browser preview up to the server to ensure
33
+ # that the latest images are available.
34
+ email.upload unless opts[:'no-upload']
35
35
 
36
36
  Inkcite::Mailer.send(email, opts.merge({ :to => send_to }))
37
37
 
@@ -2,9 +2,12 @@ require_relative 'renderer/base'
2
2
  require_relative 'renderer/element'
3
3
  require_relative 'renderer/responsive'
4
4
  require_relative 'renderer/container_base'
5
+ require_relative 'renderer/special_effect'
5
6
  require_relative 'renderer/image_base'
6
7
  require_relative 'renderer/table_base'
8
+ require_relative 'renderer/style'
7
9
 
10
+ require_relative 'renderer/background'
8
11
  require_relative 'renderer/button'
9
12
  require_relative 'renderer/div'
10
13
  require_relative 'renderer/footnote'
@@ -20,7 +23,6 @@ require_relative 'renderer/mobile_image'
20
23
  require_relative 'renderer/mobile_only'
21
24
  require_relative 'renderer/mobile_style'
22
25
  require_relative 'renderer/mobile_toggle'
23
- require_relative 'renderer/outlook_background'
24
26
  require_relative 'renderer/partial'
25
27
  require_relative 'renderer/preheader'
26
28
  require_relative 'renderer/property'
@@ -28,6 +30,7 @@ require_relative 'renderer/redacted'
28
30
  require_relative 'renderer/snow'
29
31
  require_relative 'renderer/social'
30
32
  require_relative 'renderer/span'
33
+ require_relative 'renderer/sparkle'
31
34
  require_relative 'renderer/table'
32
35
  require_relative 'renderer/td'
33
36
  require_relative 'renderer/video_preview'
@@ -68,7 +71,7 @@ module Inkcite
68
71
  def self.hex color
69
72
 
70
73
  # Convert #rgb into #rrggbb
71
- if !color.blank? && color.length < 7
74
+ if !color.blank? && color.length == 4 && color.start_with?('#')
72
75
  red = color[1]
73
76
  green = color[2]
74
77
  blue = color[3]
@@ -167,6 +170,7 @@ module Inkcite
167
170
  @renderers ||= {
168
171
  :'++' => Increment.new,
169
172
  :a => Link.new,
173
+ :background => Background.new,
170
174
  :button => Button.new,
171
175
  :div => Div.new,
172
176
  :facebook => Social::Facebook.new,
@@ -183,12 +187,12 @@ module Inkcite
183
187
  :'mobile-only' => MobileOnly.new,
184
188
  :'mobile-style' => MobileStyle.new,
185
189
  :'mobile-toggle-on' => MobileToggleOn.new,
186
- :'outlook-bg' => OutlookBackground.new,
187
190
  :pintrest => Social::Pintrest.new,
188
191
  :preheader => Preheader.new,
189
192
  :redacted => Redacted.new,
190
193
  :snow => Snow.new,
191
194
  :span => Span.new,
195
+ :sparkle => Sparkle.new,
192
196
  :table => Table.new,
193
197
  :td => Td.new,
194
198
  :twitter => Social::Twitter.new,
@@ -0,0 +1,153 @@
1
+ module Inkcite
2
+ module Renderer
3
+
4
+ # Bulletproof background image support courtesy of @stigm via Campaign Monitor
5
+ # https://backgrounds.cm/
6
+ #
7
+ # {background src=YJOX1PC.png bgcolor=#7bceeb height=92 width=120}
8
+ # ...
9
+ # {/background}
10
+ #
11
+ class Background < ImageBase
12
+
13
+ def render tag, opt, ctx
14
+
15
+ html = ''
16
+
17
+ if tag == '/background'
18
+
19
+ html << '</div>'
20
+
21
+ # If VML is enabled, then close the textbox and rect that were created
22
+ # by the opening tags.
23
+ if ctx.vml_enabled?
24
+ html << '{outlook-only}'
25
+ html << '</v:textbox>'
26
+ html << '</v:rect>'
27
+ html << '{/outlook-only}'
28
+ end
29
+
30
+ html << '{/td}'
31
+ html << '{/table}'
32
+
33
+ else
34
+
35
+ # Primary background image
36
+ src = opt[:src]
37
+
38
+ # Dimensions
39
+ width = opt[:width]
40
+ height = opt[:height].to_i
41
+
42
+ # True if the background image's width should fill the available
43
+ # horizontal space. Specified by either leaving the width blank or
44
+ # specifying 'fill' or '100%'
45
+ fill_width = width.nil? || width == 'fill' || width == '100%' || width.to_i <= 0
46
+
47
+ table = Element.new('table')
48
+ table[:height] = height if height > 0
49
+ table[:width] = (fill_width ? '100%' : width)
50
+ table[:background] = quote(src) unless none?(src)
51
+
52
+ # Iterate through the list of the parameters that are copied straight into
53
+ # the internal {table} Helper. This is a sanitized list of supported
54
+ # parameters to prevent the user from setting things inadvertently that
55
+ # might interfere with the display of the background (e.g. padding)
56
+ TABLE_PASSTHRU_OPS.each do |key|
57
+ val = opt[key]
58
+ table[key] = quote(val) unless none?(val)
59
+ end
60
+
61
+ # Determine if a fallback background color has been defined.
62
+ bgcolor = detect_bgcolor(opt)
63
+ table[:bgcolor] = quote(bgcolor) unless none?(bgcolor)
64
+
65
+ # Check for a background gradient
66
+ bggradient = detect_bggradient(opt)
67
+ table[:bggradient] = quote(bggradient) unless none?(bggradient)
68
+
69
+ td = Element.new('td')
70
+
71
+ valign = opt[:valign]
72
+ td[:valign] = valign unless valign.blank?
73
+
74
+ html << table.to_helper
75
+ html << td.to_helper
76
+
77
+ # VML is only added if it is enabled for the project.
78
+ if ctx.vml_enabled?
79
+
80
+ # Get the fully-qualified URL to the image or placeholder image if it's
81
+ # missing from the images directory. This comes back with quotes around it.
82
+ outlook_src = image_url(opt[OUTLOOK_SRC] || src, opt, ctx, false)
83
+
84
+ # True if the height of the background image will fit to content within the
85
+ # background element (specified by omitting the 'height' attribute).
86
+ fit_to_shape = height <= 0
87
+
88
+ rect = Element.new('v:rect', { :'xmlns:v' => quote('urn:schemas-microsoft-com:vml'), :fill => quote('t'), :stroke => quote('f') })
89
+
90
+ if fill_width
91
+
92
+ # The number you pass to 'mso-width-percent' is ten times the percentage you'd like.
93
+ # https://www.emailonacid.com/blog/article/email-development/emailology_vector_markup_language_and_backgrounds
94
+ rect.style[:'mso-width-percent'] = 1000
95
+
96
+ else
97
+ rect.style[:width] = px(width)
98
+
99
+ end
100
+
101
+ rect.style[:height] = px(height) unless fit_to_shape
102
+
103
+ fill = Element.new('v:fill', { :type => '"tile"', :src => outlook_src, :self_close => true })
104
+ fill[:color] = quote(bgcolor) unless none?(bgcolor)
105
+
106
+ textbox = Element.new('v:textbox', :inset => '"0,0,0,0"')
107
+ textbox.style[:'mso-fit-shape-to-text'] = 'True' if fit_to_shape
108
+
109
+ html << '{outlook-only}'
110
+ html << rect.to_s
111
+ html << fill.to_s
112
+ html << textbox.to_s
113
+ html << '{/outlook-only}'
114
+
115
+ # Flag the context as having had VML used within it.
116
+ ctx.vml_used!
117
+
118
+ end
119
+
120
+ div = Element.new('div')
121
+
122
+ # Font family and other attributes get reset within the v:textbox so allow
123
+ # the font series of attributes to be applied.
124
+ mix_font div, opt, ctx
125
+
126
+ # Text alignment within the div.
127
+ mix_text_align div, opt, ctx
128
+
129
+ html << div.to_s
130
+
131
+ end
132
+
133
+ html
134
+ end
135
+
136
+ private
137
+
138
+ # The custom
139
+ MOBILE_SRC = :'mobile-src'
140
+
141
+ # These are the parameters that are passed directly from
142
+ # the provided opt to the {table} rendered within the
143
+ # background Helper.
144
+ TABLE_PASSTHRU_OPS = [
145
+ BACKGROUND_POSITION, :border, BORDER_BOTTOM, BORDER_LEFT, BORDER_RADIUS, BORDER_RIGHT,
146
+ BORDER_SPACING, BORDER_TOP, :mobile, MOBILE_BGCOLOR, MOBILE_BACKGROUND, MOBILE_BACKGROUND_COLOR,
147
+ MOBILE_BACKGROUND_IMAGE, MOBILE_BACKGROUND_REPEAT, MOBILE_BACKGROUND_POSITION, MOBILE_PADDING,
148
+ MOBILE_SRC, MOBILE_BACKGROUND_SIZE
149
+ ]
150
+
151
+ end
152
+ end
153
+ end