inkcite 1.12.1 → 1.13.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/inkcite/animation.rb +96 -74
- data/lib/inkcite/cli/base.rb +6 -0
- data/lib/inkcite/cli/preview.rb +1 -1
- data/lib/inkcite/cli/test.rb +3 -3
- data/lib/inkcite/renderer.rb +7 -3
- data/lib/inkcite/renderer/background.rb +153 -0
- data/lib/inkcite/renderer/base.rb +58 -29
- data/lib/inkcite/renderer/container_base.rb +11 -4
- data/lib/inkcite/renderer/div.rb +1 -2
- data/lib/inkcite/renderer/element.rb +6 -7
- data/lib/inkcite/renderer/responsive.rb +124 -37
- data/lib/inkcite/renderer/snow.rb +53 -248
- data/lib/inkcite/renderer/sparkle.rb +77 -0
- data/lib/inkcite/renderer/special_effect.rb +429 -0
- data/lib/inkcite/renderer/style.rb +81 -0
- data/lib/inkcite/renderer/table_base.rb +4 -12
- data/lib/inkcite/renderer/td.rb +6 -24
- data/lib/inkcite/renderer/video_preview.rb +17 -7
- data/lib/inkcite/version.rb +1 -1
- data/lib/inkcite/view.rb +53 -18
- data/lib/inkcite/view/media_query.rb +1 -1
- data/test/animation_spec.rb +14 -10
- data/test/renderer/background_spec.rb +59 -0
- data/test/renderer/div_spec.rb +11 -1
- data/test/renderer/image_spec.rb +1 -1
- data/test/renderer/mobile_image_spec.rb +3 -3
- data/test/renderer/mobile_style_spec.rb +1 -1
- data/test/renderer/span_spec.rb +1 -1
- data/test/renderer/table_spec.rb +22 -7
- data/test/renderer/td_spec.rb +29 -8
- data/test/renderer/video_preview_spec.rb +3 -3
- metadata +8 -5
- data/lib/inkcite/renderer/outlook_background.rb +0 -96
- data/test/renderer/outlook_background_spec.rb +0 -61
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: db3a1196fe9bcc49e718925d27731a469807acdc
|
4
|
+
data.tar.gz: 4dfc1aa8998ec37e5ed03a81facaf7f75ac6bfc9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e1e085b9a0668c30f9c6bd968f962caa42205b45702c4c9cba5893bb71ea1fe0b63f6e85c90e767912c42640f36f3a94ed11b54078d189323fc1886a5d37ab7c
|
7
|
+
data.tar.gz: 92166f271e19bbcc7058136e2d77a441179a341ef8107d19c24a0f15f8ddc883c319d5410a50edfecfdac89180b1d518dd5270a1614fd09846f8f3145c3d08c6
|
data/lib/inkcite/animation.rb
CHANGED
@@ -1,135 +1,157 @@
|
|
1
1
|
module Inkcite
|
2
|
-
|
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
|
-
|
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
|
-
@
|
19
|
+
@style[key]
|
16
20
|
end
|
17
21
|
|
18
22
|
def []= key, val
|
19
|
-
@
|
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
|
-
|
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
|
-
|
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
|
51
|
+
def to_css prefix
|
52
|
+
@style.to_css(prefix)
|
53
|
+
end
|
37
54
|
|
38
|
-
|
39
|
-
css << " #{@percent}%"
|
40
|
-
css << ' ' * (7 - css.length)
|
41
|
-
css << '{ '
|
42
|
-
css << Renderer.render_styles(@styles)
|
43
|
-
css << ' }'
|
55
|
+
private
|
44
56
|
|
45
|
-
|
46
|
-
|
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
|
-
|
61
|
+
_styles = {}
|
49
62
|
|
50
|
-
|
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
|
-
|
53
|
-
@name = name
|
54
|
-
@ctx = context
|
55
|
-
@keyframes = []
|
68
|
+
_styles
|
56
69
|
end
|
57
70
|
|
58
|
-
|
59
|
-
@keyframes << keyframe
|
60
|
-
end
|
71
|
+
end
|
61
72
|
|
62
|
-
|
63
|
-
|
64
|
-
end
|
73
|
+
# Infinite iteration count
|
74
|
+
INFINITE = 'infinite'
|
65
75
|
|
66
|
-
|
76
|
+
# Timing functions
|
77
|
+
LINEAR = 'linear'
|
78
|
+
EASE = 'ease'
|
79
|
+
EASE_IN_OUT = 'ease-in-out'
|
67
80
|
|
68
|
-
|
81
|
+
# Animation name, view context and array of keyframes
|
82
|
+
attr_reader :name, :ctx
|
69
83
|
|
70
|
-
|
84
|
+
attr_accessor :duration, :timing_function, :delay, :iteration_count
|
71
85
|
|
72
|
-
|
86
|
+
def initialize name, ctx
|
87
|
+
@name = name
|
88
|
+
@ctx = ctx
|
73
89
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
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
|
-
|
81
|
-
|
96
|
+
# Initialize the keyframes
|
97
|
+
@keyframes = []
|
82
98
|
|
83
99
|
end
|
84
100
|
|
85
|
-
def
|
86
|
-
|
87
|
-
end
|
101
|
+
def add_keyframe percent, styles={}
|
102
|
+
keyframe = Keyframe.new(percent, @ctx, styles)
|
88
103
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
def self.webkit_only? ctx
|
93
|
-
false #&& !(ctx.development? || ctx.browser?)
|
104
|
+
@keyframes << keyframe
|
105
|
+
|
106
|
+
keyframe
|
94
107
|
end
|
95
108
|
|
96
|
-
|
97
|
-
|
98
|
-
|
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
|
-
|
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
|
-
|
103
|
-
indentation = ' ' * indentation if indentation.is_a?(Integer)
|
126
|
+
end
|
104
127
|
|
105
|
-
|
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
|
-
#
|
108
|
-
|
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
|
-
|
111
|
-
_css = ''
|
141
|
+
css << seconds(@delay) if @delay > 0
|
112
142
|
|
113
|
-
|
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
|
-
|
145
|
+
css << @name
|
146
|
+
|
147
|
+
css.join(' ')
|
123
148
|
end
|
124
149
|
|
125
150
|
private
|
126
151
|
|
127
|
-
|
128
|
-
|
129
|
-
|
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
|
data/lib/inkcite/cli/base.rb
CHANGED
@@ -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'
|
data/lib/inkcite/cli/preview.rb
CHANGED
data/lib/inkcite/cli/test.rb
CHANGED
@@ -29,9 +29,9 @@ module Inkcite
|
|
29
29
|
USAGE
|
30
30
|
end
|
31
31
|
|
32
|
-
#
|
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
|
|
data/lib/inkcite/renderer.rb
CHANGED
@@ -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
|
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
|