inkcite 1.10.0 → 1.11.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 +4 -4
- data/inkcite.gemspec +1 -0
- data/lib/inkcite/cli/base.rb +13 -0
- data/lib/inkcite/cli/validate.rb +70 -0
- data/lib/inkcite/renderer.rb +14 -1
- data/lib/inkcite/renderer/footnote.rb +1 -1
- data/lib/inkcite/renderer/image.rb +6 -0
- data/lib/inkcite/renderer/lorem.rb +5 -3
- data/lib/inkcite/renderer/redacted.rb +27 -0
- data/lib/inkcite/renderer/snow.rb +270 -0
- data/lib/inkcite/renderer/td.rb +3 -0
- data/lib/inkcite/version.rb +1 -1
- data/lib/inkcite/view.rb +6 -1
- data/test/renderer/footnote_spec.rb +1 -1
- data/test/renderer/lorem_spec.rb +25 -0
- data/test/renderer/redacted_spec.rb +25 -0
- data/test/renderer/td_spec.rb +4 -0
- metadata +23 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7d4ec18570e9ba801f756e6594a3737bbc3ced90
|
|
4
|
+
data.tar.gz: df1c113816730b99cb4ced73b9c9965252ea4d97
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 143670d23d2712b7eddcc6e93358c9f3cf0155df60f6917d9258e1fc0c0be5dbc7bdde461ad15b035cc458e3cfe41f4bc5420cab295e44b87b65cc1efa45bab9
|
|
7
|
+
data.tar.gz: d6bf7f13e73984a4c85097c06074b26e85e6bb43c0c12b91f764b629cdbccdc3be312db47430377a1e5996c9ed6a934c9b38843210076c690358331dceb7c4ba
|
data/inkcite.gemspec
CHANGED
|
@@ -36,6 +36,7 @@ Gem::Specification.new do |spec|
|
|
|
36
36
|
spec.add_dependency 'mail'
|
|
37
37
|
spec.add_dependency 'mailgun-ruby'
|
|
38
38
|
spec.add_dependency 'net-sftp'
|
|
39
|
+
spec.add_dependency 'nokogiri'
|
|
39
40
|
spec.add_dependency 'rack'
|
|
40
41
|
spec.add_dependency 'rack-livereload'
|
|
41
42
|
spec.add_dependency 'rubyzip'
|
data/lib/inkcite/cli/base.rb
CHANGED
|
@@ -113,6 +113,19 @@ module Inkcite
|
|
|
113
113
|
options[:force] ? email.upload! : email.upload
|
|
114
114
|
end
|
|
115
115
|
|
|
116
|
+
desc 'validate', 'Validate the email against W3C standards'
|
|
117
|
+
option :environment,
|
|
118
|
+
:aliases => '-e',
|
|
119
|
+
:default => 'production',
|
|
120
|
+
:desc => 'The environment (production, preview, development) to be Inkcite will run under'
|
|
121
|
+
option :version,
|
|
122
|
+
:aliases => '-v',
|
|
123
|
+
:desc => 'Preview a specific version of the email'
|
|
124
|
+
def validate
|
|
125
|
+
require_relative 'validate'
|
|
126
|
+
Cli::Validate.invoke(email, options)
|
|
127
|
+
end
|
|
128
|
+
|
|
116
129
|
private
|
|
117
130
|
|
|
118
131
|
# Resolves the desired environment (e.g. :development or :preview)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
require 'nokogiri'
|
|
2
|
+
|
|
3
|
+
module Inkcite
|
|
4
|
+
module Cli
|
|
5
|
+
class Validate
|
|
6
|
+
|
|
7
|
+
def self.invoke email, opts
|
|
8
|
+
|
|
9
|
+
# True if all versions of the email are valid.
|
|
10
|
+
valid = true
|
|
11
|
+
|
|
12
|
+
# Grab the environment (e.g. production) that will be validated.
|
|
13
|
+
environment = opts[:environment]
|
|
14
|
+
|
|
15
|
+
# Check to see if a specific version is requested or if unspecified
|
|
16
|
+
# all versions of the email should be validated.
|
|
17
|
+
versions = Array(opts[:version] || email.versions)
|
|
18
|
+
versions.each do |version|
|
|
19
|
+
|
|
20
|
+
# The version of the email we will be sending.
|
|
21
|
+
view = email.view(environment, :email, version)
|
|
22
|
+
|
|
23
|
+
# Always disable minification
|
|
24
|
+
view.config[:minify] = false
|
|
25
|
+
|
|
26
|
+
subject = view.subject
|
|
27
|
+
|
|
28
|
+
print "Validating '#{subject}' ... "
|
|
29
|
+
|
|
30
|
+
source = view.render!
|
|
31
|
+
|
|
32
|
+
validator = Nokogiri::HTML(source) do |config|
|
|
33
|
+
config.strict
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
if validator.errors.any?
|
|
37
|
+
puts 'Invalid!'
|
|
38
|
+
|
|
39
|
+
lines = source.split(/\n/)
|
|
40
|
+
|
|
41
|
+
validator.errors.each do |err|
|
|
42
|
+
puts err.inspect
|
|
43
|
+
puts "#{err.line}: #{lines[err.line - 1]}"
|
|
44
|
+
puts [ err.column, err.int1, err.str1, err.str2, err.str3 ].inspect
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
else
|
|
48
|
+
puts 'Valid!'
|
|
49
|
+
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
if versions.length > 1
|
|
53
|
+
puts ''
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
valid
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
# Name of the config property that
|
|
64
|
+
TEST_ADDRESS = :'test-address'
|
|
65
|
+
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
|
data/lib/inkcite/renderer.rb
CHANGED
|
@@ -23,6 +23,8 @@ require_relative 'renderer/outlook_background'
|
|
|
23
23
|
require_relative 'renderer/partial'
|
|
24
24
|
require_relative 'renderer/preheader'
|
|
25
25
|
require_relative 'renderer/property'
|
|
26
|
+
require_relative 'renderer/redacted'
|
|
27
|
+
require_relative 'renderer/snow'
|
|
26
28
|
require_relative 'renderer/span'
|
|
27
29
|
require_relative 'renderer/table'
|
|
28
30
|
require_relative 'renderer/td'
|
|
@@ -83,7 +85,16 @@ module Inkcite
|
|
|
83
85
|
|
|
84
86
|
hash.keys.sort.each do |att|
|
|
85
87
|
val = hash[att]
|
|
86
|
-
|
|
88
|
+
next if val.blank?
|
|
89
|
+
|
|
90
|
+
# First add the attribute name - e.g. "padding" or "bgcolor"
|
|
91
|
+
pair = "#{att}"
|
|
92
|
+
|
|
93
|
+
# Only append the value if the attribute value is a non-boolean.
|
|
94
|
+
# e.g. support boolean attributes via booleans ":nowrap => true"
|
|
95
|
+
pair << "#{equal}#{val}" unless val == true
|
|
96
|
+
|
|
97
|
+
pairs << pair
|
|
87
98
|
end
|
|
88
99
|
|
|
89
100
|
pairs.join(sep)
|
|
@@ -169,6 +180,8 @@ module Inkcite
|
|
|
169
180
|
:'mobile-toggle-on' => MobileToggleOn.new,
|
|
170
181
|
:'outlook-bg' => OutlookBackground.new,
|
|
171
182
|
:preheader => Preheader.new,
|
|
183
|
+
:redacted => Redacted.new,
|
|
184
|
+
:snow => Snow.new,
|
|
172
185
|
:span => Span.new,
|
|
173
186
|
:table => Table.new,
|
|
174
187
|
:td => Td.new
|
|
@@ -22,6 +22,12 @@ module Inkcite
|
|
|
22
22
|
alt = opt[:alt]
|
|
23
23
|
if alt
|
|
24
24
|
|
|
25
|
+
# Allow "\n" to be placed within alt text and converted into a line
|
|
26
|
+
# break for convenience. Need to add an extra space for the email
|
|
27
|
+
# clients (ahem, Gmail, cough) that don't support alt text with
|
|
28
|
+
# line breaks.
|
|
29
|
+
alt.gsub!('\n', "\n ")
|
|
30
|
+
|
|
25
31
|
# Ensure that the alt-tag has quotes around it.
|
|
26
32
|
img[:alt] = quote(alt)
|
|
27
33
|
|
|
@@ -14,15 +14,17 @@ module Inkcite
|
|
|
14
14
|
|
|
15
15
|
# Always warn the creator that there is Lorem Ipsum in the email because
|
|
16
16
|
# we don't want it to ship accidentally.
|
|
17
|
-
ctx.error 'Email contains Lorem Ipsum'
|
|
17
|
+
ctx.error 'Email contains Lorem Ipsum' unless opt[:force]
|
|
18
18
|
|
|
19
19
|
if type == :headline
|
|
20
|
-
|
|
21
20
|
words = (limit || 4).to_i
|
|
22
21
|
Faker::Lorem.words(words).join(SPACE).titlecase
|
|
23
22
|
|
|
24
|
-
|
|
23
|
+
elsif type == :words
|
|
24
|
+
words = (limit || 7).to_i
|
|
25
|
+
Faker::Lorem.words(words).join(SPACE)
|
|
25
26
|
|
|
27
|
+
else
|
|
26
28
|
sentences = (limit || 3).to_i
|
|
27
29
|
Faker::Lorem.sentences(sentences).join(SPACE)
|
|
28
30
|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Inkcite
|
|
2
|
+
module Renderer
|
|
3
|
+
class Redacted < Base
|
|
4
|
+
|
|
5
|
+
def render tag, opt, ctx
|
|
6
|
+
|
|
7
|
+
# Like Lorem Ipsum, warn the creator that there is redaction in
|
|
8
|
+
# the email unless the warn parameter is true.
|
|
9
|
+
ctx.error 'Email contains redacted content' unless opt[:force]
|
|
10
|
+
|
|
11
|
+
# The obscuring character defaults to 'x' but can be overridden
|
|
12
|
+
# using the 'with' attribute.
|
|
13
|
+
with = opt[:with] || 'x'
|
|
14
|
+
|
|
15
|
+
# Grab the text to be redacted, then apply the correct obscuring
|
|
16
|
+
# character based on the case of the original letters.
|
|
17
|
+
text = opt[:text]
|
|
18
|
+
text.gsub!(/[A-Z]/, with.upcase)
|
|
19
|
+
text.gsub!(/[a-z0-9]/, with.downcase)
|
|
20
|
+
text
|
|
21
|
+
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
module Inkcite
|
|
2
|
+
module Renderer
|
|
3
|
+
class Snow < ContainerBase
|
|
4
|
+
|
|
5
|
+
# Ambient snow special effect renderer courtesy of
|
|
6
|
+
# http://freshinbox.com/blog/ambient-animations-in-email-snow-and-stars/
|
|
7
|
+
def render tag, opt, ctx
|
|
8
|
+
|
|
9
|
+
return '</div>' if tag == '/snow'
|
|
10
|
+
|
|
11
|
+
# Get a unique ID for this wrap element.
|
|
12
|
+
uid = ctx.unique_id(:snow)
|
|
13
|
+
|
|
14
|
+
# Total number of flakes to animate
|
|
15
|
+
flakes = (opt[:flakes] || 3).to_i
|
|
16
|
+
|
|
17
|
+
# This is the general class applied to all snow elements within this
|
|
18
|
+
# wrapping container.
|
|
19
|
+
all_flakes_class = ctx.development? ? "snow#{uid}-flakes" : "s#{uid}fs"
|
|
20
|
+
flake_prefix = ctx.development? ? "snow#{uid}-flake" : "s#{uid}f"
|
|
21
|
+
anim_prefix = ctx.development? ? "snow#{uid}-anim" : "s#{uid}a"
|
|
22
|
+
|
|
23
|
+
# Grab the min and max sizes for the flakes or inherit default values.
|
|
24
|
+
flake_min_size = (opt[FLAKE_SIZE_MIN] || 6).to_i
|
|
25
|
+
flake_max_size = (opt[FLAKE_SIZE_MAX] || 18).to_i
|
|
26
|
+
|
|
27
|
+
# Grab the min and max speeds for the flakes, the smaller the value the
|
|
28
|
+
# faster the flake moves.
|
|
29
|
+
flake_min_speed = (opt[FLAKE_SPEED_MIN] || 3).to_f
|
|
30
|
+
flake_max_speed = (opt[FLAKE_SPEED_MAX] || 8).to_f
|
|
31
|
+
|
|
32
|
+
# Determine the spread of the flakes - the bigger the spread, the larger
|
|
33
|
+
# the variance between where the flake starts and where it ends.
|
|
34
|
+
# Measured in %-width of the overall area.
|
|
35
|
+
spread = (opt[:spread] || 20).to_i
|
|
36
|
+
half_spread = spread / 2.0
|
|
37
|
+
|
|
38
|
+
# Determine the opacity variance.
|
|
39
|
+
flake_min_opacity = (opt[FLAKE_OPACITY_MIN] || 0.5).to_f
|
|
40
|
+
flake_max_opacity = (opt[FLAKE_OPACITY_MAX] || 0.9).to_f
|
|
41
|
+
|
|
42
|
+
# Overall time for the initial distribution of flakes.
|
|
43
|
+
end_time = (opt[:time] || 4).to_f
|
|
44
|
+
|
|
45
|
+
# Setup some ranges for easier random numbering.
|
|
46
|
+
size_range = (flake_min_size..flake_max_size)
|
|
47
|
+
speed_range = (flake_min_speed..flake_max_speed)
|
|
48
|
+
spread_range = (-half_spread..half_spread)
|
|
49
|
+
opacity_range = (flake_min_opacity..flake_max_opacity)
|
|
50
|
+
|
|
51
|
+
# Snowflake color.
|
|
52
|
+
color = hex(opt[:color] || '#fff')
|
|
53
|
+
|
|
54
|
+
# Check to see if a source image has been specified for the snowflakes.
|
|
55
|
+
src = opt[:src]
|
|
56
|
+
|
|
57
|
+
# If the image is missing, record an error to the console and
|
|
58
|
+
# clear the image allowing the color to take precedence instead.
|
|
59
|
+
src = nil unless ctx.assert_image_exists(src)
|
|
60
|
+
|
|
61
|
+
# Flake rotation, used if an image is present. (Rotating a colored
|
|
62
|
+
# circle has no visual distinction.) For convenience this tag accepts
|
|
63
|
+
# boolean rotate and rotation
|
|
64
|
+
rotation_enabled = src && (opt[:rotate] || opt[:rotation])
|
|
65
|
+
|
|
66
|
+
# Initialize the wrap that will hold each of the snowflakes and the
|
|
67
|
+
# content within that will have it's
|
|
68
|
+
div_wrap = Element.new('div')
|
|
69
|
+
|
|
70
|
+
# Resolve the wrapping class name - readable name in development,
|
|
71
|
+
# space-saving name in all other environments.
|
|
72
|
+
wrap_class = ctx.development? ? "snow#{uid}-wrap" : "s#{uid}w"
|
|
73
|
+
div_wrap[:class] = wrap_class
|
|
74
|
+
|
|
75
|
+
# Background color gets applied directly to the div so it renders
|
|
76
|
+
# consistently in all clients - even those that don't support the
|
|
77
|
+
# snow effect.
|
|
78
|
+
mix_background div_wrap, opt, ctx
|
|
79
|
+
|
|
80
|
+
# Kick things off by rendering the wrapping div.
|
|
81
|
+
html = div_wrap.to_s
|
|
82
|
+
|
|
83
|
+
# Get the number of flakes that should be included. Create a child div for
|
|
84
|
+
# each flake that can be sized, animated uniquely.
|
|
85
|
+
flakes.times do |flake|
|
|
86
|
+
html << %Q(<div class="#{all_flakes_class} #{flake_prefix}#{flake + 1}"></div>)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Check to see if there is a height required for the wrap element - otherwise
|
|
90
|
+
# the wrap will simply enlarge to hold all of the contents within.
|
|
91
|
+
wrap_height = opt[:height].to_i
|
|
92
|
+
|
|
93
|
+
# Will hold all of the styles as they're assembled.
|
|
94
|
+
style = []
|
|
95
|
+
|
|
96
|
+
# True if we're limiting the animation to webkit only. In development
|
|
97
|
+
# or in the browser version of the email, the animation should be as
|
|
98
|
+
# compatible as possible but in all other cases it should be webkit only.
|
|
99
|
+
webkit_only = !(ctx.development? || ctx.browser?)
|
|
100
|
+
|
|
101
|
+
# Hide the snow effect from any non-webkit email clients.
|
|
102
|
+
style << '@media screen and (-webkit-min-device-pixel-ratio: 0) {' if webkit_only
|
|
103
|
+
|
|
104
|
+
# Snow wrapping element in-which the snow flakes will be animated.
|
|
105
|
+
style << " .#{wrap_class} {"
|
|
106
|
+
style << ' position: relative;'
|
|
107
|
+
style << ' overflow: hidden;'
|
|
108
|
+
style << ' width: 100%;'
|
|
109
|
+
style << " height: #{px(wrap_height)};" if wrap_height > 0
|
|
110
|
+
style << ' }'
|
|
111
|
+
|
|
112
|
+
# Common attributes for all snowflakes.
|
|
113
|
+
style << " .#{all_flakes_class} {"
|
|
114
|
+
style << ' position: absolute;'
|
|
115
|
+
style << " top: -#{flake_max_size + 4}px;"
|
|
116
|
+
|
|
117
|
+
# If no image has been provided, make the background a solid color
|
|
118
|
+
# otherwise set the background to the image source and fill the
|
|
119
|
+
# available space.
|
|
120
|
+
if src.blank?
|
|
121
|
+
style << " background-color: #{color};"
|
|
122
|
+
else
|
|
123
|
+
style << " background-image: url(#{ctx.image_url(src)});"
|
|
124
|
+
style << " background-size: 100%;"
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
style << ' }'
|
|
128
|
+
|
|
129
|
+
# Space the snowflakes generally equally across the width of the
|
|
130
|
+
# container div. Random distribution sometimes ends up with
|
|
131
|
+
# snowflakes clumped at one edge or the other.
|
|
132
|
+
flake_spacing = 100 / flakes.to_f
|
|
133
|
+
|
|
134
|
+
# Now build up a pool of equally-spaced starting positions.
|
|
135
|
+
# TODO: This is probably a perfect spot to use inject()
|
|
136
|
+
start_left = flake_spacing / 2.0
|
|
137
|
+
start_positions = [start_left]
|
|
138
|
+
(flakes - 1).times { |f| start_positions << start_left += flake_spacing }
|
|
139
|
+
|
|
140
|
+
# Randomize the starting positions - otherwise they draw right-to-left
|
|
141
|
+
# as starting positions are popped from the pool.
|
|
142
|
+
start_positions.shuffle!
|
|
143
|
+
|
|
144
|
+
# Snowflakes will be dispersed equally across the total time
|
|
145
|
+
# of the animation making for a smoother, more balanced show.
|
|
146
|
+
start_interval = end_time / flakes.to_f
|
|
147
|
+
start_time = 0
|
|
148
|
+
|
|
149
|
+
# Now add individual class definitions for each flake with unique size,
|
|
150
|
+
# speed and starting position. Also add the animation trigger that loops
|
|
151
|
+
# infinitely, starts at a random time and uses a random speed to completion.
|
|
152
|
+
flakes.times do |flake|
|
|
153
|
+
|
|
154
|
+
speed = rand(speed_range).round(1)
|
|
155
|
+
size = rand(size_range)
|
|
156
|
+
|
|
157
|
+
opacity = rand(opacity_range).round(1)
|
|
158
|
+
if opacity < OPACITY_FLOOR
|
|
159
|
+
opacity = OPACITY_FLOOR
|
|
160
|
+
elsif opacity > OPACITY_CEIL
|
|
161
|
+
opacity = OPACITY_CEIL
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
style << " .#{flake_prefix}#{flake + 1} {"
|
|
165
|
+
style << " height: #{px(size)};"
|
|
166
|
+
style << " width: #{px(size)};"
|
|
167
|
+
|
|
168
|
+
# Only apply a border radius if the snowflake lacks an image.
|
|
169
|
+
style << " border-radius: #{px((size / 2.0).round)};" unless src
|
|
170
|
+
|
|
171
|
+
style << " opacity: #{opacity};" if opacity < OPACITY_CEIL
|
|
172
|
+
style << with_browser_prefixes(' ', "animation: #{anim_prefix}#{flake + 1} #{speed}s linear #{start_time.round(1)}s infinite;", webkit_only)
|
|
173
|
+
style << ' }'
|
|
174
|
+
|
|
175
|
+
start_time += start_interval
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Declare each of the flake animations.
|
|
179
|
+
flakes.times do |flake|
|
|
180
|
+
|
|
181
|
+
start_left = start_positions.pop
|
|
182
|
+
|
|
183
|
+
# Randomly choose where the snow will end its animation. Prevent
|
|
184
|
+
# it from going outside of the container.
|
|
185
|
+
end_left = (start_left + rand(spread_range)).round
|
|
186
|
+
if end_left < POSITION_FLOOR
|
|
187
|
+
end_left = POSITION_FLOOR
|
|
188
|
+
elsif end_left > POSITION_CEIL
|
|
189
|
+
end_left = POSITION_CEIL
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Calculate the ending rotation for the flake, if rotation is enabled.
|
|
193
|
+
end_rotation = rotation_enabled ? rand(ROTATION_RANGE) : 0
|
|
194
|
+
|
|
195
|
+
_style = "keyframes #{anim_prefix}#{flake + 1} {\n"
|
|
196
|
+
_style << " 0% { top: -3%; left: #{start_left}%; }\n"
|
|
197
|
+
_style << " 100% { top: 100%; left: #{end_left}%;"
|
|
198
|
+
_style << with_browser_prefixes(' ', "transform: rotate(#{end_rotation}deg);", webkit_only, '') if end_rotation != 0
|
|
199
|
+
_style << " }\n"
|
|
200
|
+
_style << ' }'
|
|
201
|
+
|
|
202
|
+
style << with_browser_prefixes(" @", _style, webkit_only)
|
|
203
|
+
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
style << '}' if webkit_only
|
|
207
|
+
|
|
208
|
+
ctx.styles << style.join("\n")
|
|
209
|
+
|
|
210
|
+
html
|
|
211
|
+
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
private
|
|
215
|
+
|
|
216
|
+
# Renders the CSS with the appropriate browser prefixes based
|
|
217
|
+
# on whether or not this version of the email is webkit only.
|
|
218
|
+
def with_browser_prefixes indentation, css, webkit_only, separator="\n"
|
|
219
|
+
|
|
220
|
+
# Determine which prefixes will be applied.
|
|
221
|
+
browser_prefixes = webkit_only ? WEBKIT_BROWSERS : ALL_BROWSERS
|
|
222
|
+
|
|
223
|
+
# This will hold the completed CSS with all prefixes applied.
|
|
224
|
+
_css = ''
|
|
225
|
+
|
|
226
|
+
# Iterate through the prefixes and apply them with the indentation
|
|
227
|
+
# and CSS declaration with line breaks.
|
|
228
|
+
browser_prefixes.each do |prefix|
|
|
229
|
+
_css << indentation
|
|
230
|
+
_css << prefix
|
|
231
|
+
_css << css
|
|
232
|
+
_css << separator
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
_css
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Size constraints on the flakes.
|
|
239
|
+
FLAKE_SIZE_MIN = :'min-size'
|
|
240
|
+
FLAKE_SIZE_MAX = :'max-size'
|
|
241
|
+
|
|
242
|
+
# Speed constraints on the flakes.
|
|
243
|
+
FLAKE_SPEED_MIN = :'min-speed'
|
|
244
|
+
FLAKE_SPEED_MAX = :'max-speed'
|
|
245
|
+
|
|
246
|
+
# Opacity constraints.
|
|
247
|
+
FLAKE_OPACITY_MIN = :'min-opacity'
|
|
248
|
+
FLAKE_OPACITY_MAX = :'max-opacity'
|
|
249
|
+
OPACITY_FLOOR = 0.2
|
|
250
|
+
OPACITY_CEIL = 1.0
|
|
251
|
+
|
|
252
|
+
# Rotation angles when image rotation is enabled.
|
|
253
|
+
ROTATION_RANGE = (-270..270)
|
|
254
|
+
|
|
255
|
+
# Position min and max preventing snow flakes
|
|
256
|
+
# from leaving the bounds of the container.
|
|
257
|
+
POSITION_FLOOR = 0
|
|
258
|
+
POSITION_CEIL = 100
|
|
259
|
+
|
|
260
|
+
# Static arrays with browser prefixes. Turns out that
|
|
261
|
+
# Firefox, IE and Opera don't require a prefix so to
|
|
262
|
+
# target everything we need the non-prefixed version
|
|
263
|
+
# (hence the blank entry) plus the webkit prefix.
|
|
264
|
+
WEBKIT_BROWSERS = ['-webkit-']
|
|
265
|
+
ALL_BROWSERS = [''] + WEBKIT_BROWSERS
|
|
266
|
+
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
end
|
|
270
|
+
end
|
data/lib/inkcite/renderer/td.rb
CHANGED
|
@@ -68,6 +68,9 @@ module Inkcite
|
|
|
68
68
|
padding = get_padding(table_opt)
|
|
69
69
|
td.style[:padding] = px(padding) if padding > 0
|
|
70
70
|
|
|
71
|
+
# Apply the no-wrap attribute if provided.
|
|
72
|
+
td[:nowrap] = true if opt[:nowrap]
|
|
73
|
+
|
|
71
74
|
mobile = opt[:mobile]
|
|
72
75
|
|
|
73
76
|
# Need to handle Fluid-Drop HTML injection here before the rest of the
|
data/lib/inkcite/version.rb
CHANGED
data/lib/inkcite/view.rb
CHANGED
|
@@ -841,7 +841,7 @@ module Inkcite
|
|
|
841
841
|
path = @email.path
|
|
842
842
|
file = File.join(path, filename)
|
|
843
843
|
unless File.exists?(file)
|
|
844
|
-
|
|
844
|
+
error("Unable to load helper file", :file => file) if abort_on_fail
|
|
845
845
|
return
|
|
846
846
|
end
|
|
847
847
|
|
|
@@ -893,6 +893,11 @@ module Inkcite
|
|
|
893
893
|
:n => NEW_LINE
|
|
894
894
|
}
|
|
895
895
|
|
|
896
|
+
# Check to see if the config.yml includes a "helpers:" array which allows
|
|
897
|
+
# additional out-of-project, shared helper files to be loaded.
|
|
898
|
+
included_helpers = [*@email.config[:helpers]]
|
|
899
|
+
included_helpers.each { |file| load_helper_file(file, _helpers) }
|
|
900
|
+
|
|
896
901
|
# Load the project's properties, which may include references to additional
|
|
897
902
|
# properties in other directories.
|
|
898
903
|
load_helper_file 'helpers.tsv', _helpers
|
|
@@ -50,7 +50,7 @@ describe Inkcite::Renderer::Footnote do
|
|
|
50
50
|
end
|
|
51
51
|
|
|
52
52
|
it 'can be defined silently' do
|
|
53
|
-
Inkcite::Renderer.render('{footnote hidden
|
|
53
|
+
Inkcite::Renderer.render('{footnote hidden text="EPA-estimated fuel economy."}{footnotes}', @view).must_equal("<sup>1</sup> EPA-estimated fuel economy.<br><br>\n")
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
it 'converts "\n" within footnotes template to new-lines' do
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
require 'minitest/spec'
|
|
2
|
+
require 'minitest/autorun'
|
|
3
|
+
require 'inkcite'
|
|
4
|
+
|
|
5
|
+
describe Inkcite::Renderer::Lorem do
|
|
6
|
+
|
|
7
|
+
before do
|
|
8
|
+
@view = Inkcite::Email.new('test/project/').view(:development, :email)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it 'produces placeholder text' do
|
|
12
|
+
Inkcite::Renderer.render('{lorem}', @view).wont_equal('')
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it 'triggers an error when included' do
|
|
16
|
+
Inkcite::Renderer.render('{lorem}', @view)
|
|
17
|
+
@view.errors.must_include('Email contains Lorem Ipsum (line 0)')
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it 'does not trigger an error when included and forced' do
|
|
21
|
+
Inkcite::Renderer.render('{lorem force}', @view)
|
|
22
|
+
@view.errors.must_be_nil
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
require 'minitest/spec'
|
|
2
|
+
require 'minitest/autorun'
|
|
3
|
+
require 'inkcite'
|
|
4
|
+
|
|
5
|
+
describe Inkcite::Renderer::Redacted do
|
|
6
|
+
|
|
7
|
+
before do
|
|
8
|
+
@view = Inkcite::Email.new('test/project/').view(:development, :email)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it 'renders redacted content' do
|
|
12
|
+
Inkcite::Renderer.render('{redacted text="This Is Redacted Text."}', @view).must_equal('Xxxx Xx Xxxxxxxx Xxxx.')
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it 'triggers an error when included' do
|
|
16
|
+
Inkcite::Renderer.render('{redacted text="This is redacted text."}', @view)
|
|
17
|
+
@view.errors.must_include('Email contains redacted content (line 0)')
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it 'does not trigger an error when included and forced' do
|
|
21
|
+
Inkcite::Renderer.render('{redacted force text="This is redacted text."}', @view)
|
|
22
|
+
@view.errors.must_be_nil
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
end
|
data/test/renderer/td_spec.rb
CHANGED
|
@@ -127,4 +127,8 @@ describe Inkcite::Renderer::Td do
|
|
|
127
127
|
@view.media_query.find_by_klass('m1').to_css.must_equal('td[class~="m1"] { background:none !important }')
|
|
128
128
|
end
|
|
129
129
|
|
|
130
|
+
it 'supports nowrap' do
|
|
131
|
+
Inkcite::Renderer.render('{td nowrap}', @view).must_equal('<td nowrap>')
|
|
132
|
+
end
|
|
133
|
+
|
|
130
134
|
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.
|
|
4
|
+
version: 1.11.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: 2016-
|
|
11
|
+
date: 2016-02-10 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activesupport
|
|
@@ -192,6 +192,20 @@ dependencies:
|
|
|
192
192
|
- - '>='
|
|
193
193
|
- !ruby/object:Gem::Version
|
|
194
194
|
version: '0'
|
|
195
|
+
- !ruby/object:Gem::Dependency
|
|
196
|
+
name: nokogiri
|
|
197
|
+
requirement: !ruby/object:Gem::Requirement
|
|
198
|
+
requirements:
|
|
199
|
+
- - '>='
|
|
200
|
+
- !ruby/object:Gem::Version
|
|
201
|
+
version: '0'
|
|
202
|
+
type: :runtime
|
|
203
|
+
prerelease: false
|
|
204
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
205
|
+
requirements:
|
|
206
|
+
- - '>='
|
|
207
|
+
- !ruby/object:Gem::Version
|
|
208
|
+
version: '0'
|
|
195
209
|
- !ruby/object:Gem::Dependency
|
|
196
210
|
name: rack
|
|
197
211
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -336,6 +350,7 @@ files:
|
|
|
336
350
|
- lib/inkcite/cli/scope.rb
|
|
337
351
|
- lib/inkcite/cli/server.rb
|
|
338
352
|
- lib/inkcite/cli/test.rb
|
|
353
|
+
- lib/inkcite/cli/validate.rb
|
|
339
354
|
- lib/inkcite/email.rb
|
|
340
355
|
- lib/inkcite/mailer.rb
|
|
341
356
|
- lib/inkcite/minifier.rb
|
|
@@ -363,7 +378,9 @@ files:
|
|
|
363
378
|
- lib/inkcite/renderer/partial.rb
|
|
364
379
|
- lib/inkcite/renderer/preheader.rb
|
|
365
380
|
- lib/inkcite/renderer/property.rb
|
|
381
|
+
- lib/inkcite/renderer/redacted.rb
|
|
366
382
|
- lib/inkcite/renderer/responsive.rb
|
|
383
|
+
- lib/inkcite/renderer/snow.rb
|
|
367
384
|
- lib/inkcite/renderer/span.rb
|
|
368
385
|
- lib/inkcite/renderer/table.rb
|
|
369
386
|
- lib/inkcite/renderer/table_base.rb
|
|
@@ -389,8 +406,10 @@ files:
|
|
|
389
406
|
- test/renderer/footnote_spec.rb
|
|
390
407
|
- test/renderer/image_spec.rb
|
|
391
408
|
- test/renderer/link_spec.rb
|
|
409
|
+
- test/renderer/lorem_spec.rb
|
|
392
410
|
- test/renderer/mobile_image_spec.rb
|
|
393
411
|
- test/renderer/mobile_style_spec.rb
|
|
412
|
+
- test/renderer/redacted_spec.rb
|
|
394
413
|
- test/renderer/span_spec.rb
|
|
395
414
|
- test/renderer/table_spec.rb
|
|
396
415
|
- test/renderer/td_spec.rb
|
|
@@ -435,8 +454,10 @@ test_files:
|
|
|
435
454
|
- test/renderer/footnote_spec.rb
|
|
436
455
|
- test/renderer/image_spec.rb
|
|
437
456
|
- test/renderer/link_spec.rb
|
|
457
|
+
- test/renderer/lorem_spec.rb
|
|
438
458
|
- test/renderer/mobile_image_spec.rb
|
|
439
459
|
- test/renderer/mobile_style_spec.rb
|
|
460
|
+
- test/renderer/redacted_spec.rb
|
|
440
461
|
- test/renderer/span_spec.rb
|
|
441
462
|
- test/renderer/table_spec.rb
|
|
442
463
|
- test/renderer/td_spec.rb
|