inkcite 1.10.0 → 1.11.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|