mjml-rb 0.2.15 → 0.2.16
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/lib/mjml-rb/components/section.rb +252 -38
- data/lib/mjml-rb/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3808a2e62a0c9a19970937ba5890b4257e8623442efc738e7725b20b11f412e0
|
|
4
|
+
data.tar.gz: 82f3f5e33d4f0b302cc0329dde2b4402713349850865cf591ade0762650f8579
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 47b7c807c43e56df28c60a7c0fae60002c8a95782bc733e293aff5f3b41e06952fd94bedd421fb669fe3002ce261708146d628bce64a79a929706530f6069028
|
|
7
|
+
data.tar.gz: 740e73174150f6130b39f8be696e584a75f56a5daa5387bfd45458f1a44706f84d2c6d25e000229f9ba3a5b28036236de7fa16deb3c1158979d3a1d55e9afd6c
|
|
@@ -7,6 +7,12 @@ module MjmlRb
|
|
|
7
7
|
|
|
8
8
|
SECTION_ALLOWED_ATTRIBUTES = {
|
|
9
9
|
"background-color" => "color",
|
|
10
|
+
"background-url" => "string",
|
|
11
|
+
"background-repeat" => "enum(repeat,no-repeat)",
|
|
12
|
+
"background-size" => "string",
|
|
13
|
+
"background-position" => "string",
|
|
14
|
+
"background-position-x" => "string",
|
|
15
|
+
"background-position-y" => "string",
|
|
10
16
|
"border" => "string",
|
|
11
17
|
"border-bottom" => "string",
|
|
12
18
|
"border-left" => "string",
|
|
@@ -27,9 +33,12 @@ module MjmlRb
|
|
|
27
33
|
).freeze
|
|
28
34
|
|
|
29
35
|
DEFAULT_ATTRIBUTES = {
|
|
30
|
-
"direction"
|
|
31
|
-
"padding"
|
|
32
|
-
"text-align"
|
|
36
|
+
"direction" => "ltr",
|
|
37
|
+
"padding" => "20px 0",
|
|
38
|
+
"text-align" => "center",
|
|
39
|
+
"background-repeat" => "repeat",
|
|
40
|
+
"background-size" => "auto",
|
|
41
|
+
"background-position" => "top center"
|
|
33
42
|
}.freeze
|
|
34
43
|
|
|
35
44
|
class << self
|
|
@@ -116,6 +125,166 @@ module MjmlRb
|
|
|
116
125
|
" #{parts.join(' ')} "
|
|
117
126
|
end
|
|
118
127
|
|
|
128
|
+
# ── Background helpers ───────────────────────────────────────────────
|
|
129
|
+
|
|
130
|
+
VERTICAL_KEYWORDS = %w[top bottom].freeze
|
|
131
|
+
HORIZONTAL_KEYWORDS = %w[left right].freeze
|
|
132
|
+
|
|
133
|
+
def has_background?(a)
|
|
134
|
+
url = a["background-url"]
|
|
135
|
+
url && !url.to_s.strip.empty?
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def parse_background_position(position_str)
|
|
139
|
+
tokens = position_str.to_s.strip.split(/\s+/)
|
|
140
|
+
|
|
141
|
+
case tokens.size
|
|
142
|
+
when 0
|
|
143
|
+
{x: "center", y: "top"}
|
|
144
|
+
when 1
|
|
145
|
+
if VERTICAL_KEYWORDS.include?(tokens[0])
|
|
146
|
+
{x: "center", y: tokens[0]}
|
|
147
|
+
else
|
|
148
|
+
{x: tokens[0], y: "center"}
|
|
149
|
+
end
|
|
150
|
+
when 2
|
|
151
|
+
first, second = tokens
|
|
152
|
+
if VERTICAL_KEYWORDS.include?(first) ||
|
|
153
|
+
(first == "center" && HORIZONTAL_KEYWORDS.include?(second))
|
|
154
|
+
{x: second, y: first}
|
|
155
|
+
else
|
|
156
|
+
{x: first, y: second}
|
|
157
|
+
end
|
|
158
|
+
else
|
|
159
|
+
{x: "center", y: "top"}
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def get_background_position(a)
|
|
164
|
+
base = parse_background_position(a["background-position"] || "top center")
|
|
165
|
+
pos_x = a["background-position-x"]
|
|
166
|
+
pos_y = a["background-position-y"]
|
|
167
|
+
x = (pos_x && !pos_x.to_s.empty?) ? pos_x : base[:x]
|
|
168
|
+
y = (pos_y && !pos_y.to_s.empty?) ? pos_y : base[:y]
|
|
169
|
+
{x: x, y: y}
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def get_background_string(a)
|
|
173
|
+
pos = get_background_position(a)
|
|
174
|
+
"#{pos[:x]} #{pos[:y]}"
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def get_background(a)
|
|
178
|
+
bg_url = a["background-url"]
|
|
179
|
+
bg_color = a["background-color"]
|
|
180
|
+
bg_size = a["background-size"]
|
|
181
|
+
bg_repeat = a["background-repeat"]
|
|
182
|
+
|
|
183
|
+
if has_background?(a)
|
|
184
|
+
pos_str = get_background_string(a)
|
|
185
|
+
parts = []
|
|
186
|
+
parts << bg_color if bg_color && !bg_color.to_s.empty?
|
|
187
|
+
parts << "url('#{bg_url}')"
|
|
188
|
+
parts << pos_str
|
|
189
|
+
parts << "/ #{bg_size}"
|
|
190
|
+
parts << bg_repeat
|
|
191
|
+
parts.join(" ")
|
|
192
|
+
else
|
|
193
|
+
bg_color
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# ── VML background for Outlook ───────────────────────────────────────
|
|
198
|
+
|
|
199
|
+
PERCENTAGE_RE = /\A\d+(\.\d+)?%\z/
|
|
200
|
+
|
|
201
|
+
VML_KEYWORD_TO_PERCENT = {
|
|
202
|
+
"left" => "0%", "top" => "0%",
|
|
203
|
+
"center" => "50%",
|
|
204
|
+
"right" => "100%", "bottom" => "100%"
|
|
205
|
+
}.freeze
|
|
206
|
+
|
|
207
|
+
def render_with_background(section_html, a, container_px)
|
|
208
|
+
bg_url = a["background-url"]
|
|
209
|
+
bg_color = a["background-color"]
|
|
210
|
+
bg_repeat = a["background-repeat"] || "repeat"
|
|
211
|
+
bg_size = a["background-size"] || "auto"
|
|
212
|
+
is_repeat = bg_repeat == "repeat"
|
|
213
|
+
|
|
214
|
+
pos = get_background_position(a)
|
|
215
|
+
|
|
216
|
+
# Normalize keywords to percentages
|
|
217
|
+
bg_pos_x = VML_KEYWORD_TO_PERCENT.fetch(pos[:x], nil) || (pos[:x] =~ PERCENTAGE_RE ? pos[:x] : "50%")
|
|
218
|
+
bg_pos_y = VML_KEYWORD_TO_PERCENT.fetch(pos[:y], nil) || (pos[:y] =~ PERCENTAGE_RE ? pos[:y] : "0%")
|
|
219
|
+
|
|
220
|
+
# Compute VML origin/position per axis
|
|
221
|
+
v_origin_x, v_pos_x = vml_axis_values(bg_pos_x, is_repeat, true)
|
|
222
|
+
v_origin_y, v_pos_y = vml_axis_values(bg_pos_y, is_repeat, false)
|
|
223
|
+
|
|
224
|
+
# VML size attributes
|
|
225
|
+
v_size_attrs = vml_size_attributes(bg_size)
|
|
226
|
+
|
|
227
|
+
# VML type
|
|
228
|
+
is_auto = bg_size == "auto"
|
|
229
|
+
vml_type = (!is_repeat && !is_auto) ? "frame" : "tile"
|
|
230
|
+
|
|
231
|
+
# Auto special case: force tile, reset position
|
|
232
|
+
if is_auto
|
|
233
|
+
v_origin_x = 0.5; v_pos_x = 0.5
|
|
234
|
+
v_origin_y = 0; v_pos_y = 0
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# Build v:fill attributes
|
|
238
|
+
fill_pairs = [
|
|
239
|
+
["origin", "#{v_origin_x}, #{v_origin_y}"],
|
|
240
|
+
["position", "#{v_pos_x}, #{v_pos_y}"],
|
|
241
|
+
["src", bg_url],
|
|
242
|
+
["color", bg_color],
|
|
243
|
+
["type", vml_type]
|
|
244
|
+
]
|
|
245
|
+
fill_pairs << ["size", v_size_attrs[:size]] if v_size_attrs[:size]
|
|
246
|
+
fill_pairs << ["aspect", v_size_attrs[:aspect]] if v_size_attrs[:aspect]
|
|
247
|
+
fill_str = fill_pairs.map { |(k, v)| %(#{k}="#{escape_attr(v.to_s)}") }.join(" ")
|
|
248
|
+
|
|
249
|
+
%(<!--[if mso | IE]><v:rect style="mso-width-percent:1000;" xmlns:v="urn:schemas-microsoft-com:vml" fill="true" stroke="false"><v:fill #{fill_str} /><v:textbox style="mso-fit-shape-to-text:true" inset="0,0,0,0"><![endif]-->) +
|
|
250
|
+
section_html +
|
|
251
|
+
%(<!--[if mso | IE]></v:textbox></v:rect><![endif]-->)
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def vml_axis_values(pct_str, is_repeat, is_x)
|
|
255
|
+
if pct_str =~ PERCENTAGE_RE
|
|
256
|
+
decimal = pct_str.to_f / 100.0
|
|
257
|
+
if is_repeat
|
|
258
|
+
[decimal, decimal]
|
|
259
|
+
else
|
|
260
|
+
val = (-50 + decimal * 100) / 100.0
|
|
261
|
+
[val, val]
|
|
262
|
+
end
|
|
263
|
+
elsif is_repeat
|
|
264
|
+
[is_x ? 0.5 : 0, is_x ? 0.5 : 0]
|
|
265
|
+
else
|
|
266
|
+
[is_x ? 0 : -0.5, is_x ? 0 : -0.5]
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def vml_size_attributes(bg_size)
|
|
271
|
+
case bg_size
|
|
272
|
+
when "cover"
|
|
273
|
+
{size: "1,1", aspect: "atleast"}
|
|
274
|
+
when "contain"
|
|
275
|
+
{size: "1,1", aspect: "atmost"}
|
|
276
|
+
when "auto"
|
|
277
|
+
{}
|
|
278
|
+
else
|
|
279
|
+
parts = bg_size.to_s.strip.split(/\s+/)
|
|
280
|
+
if parts.size == 1
|
|
281
|
+
{size: bg_size, aspect: "atmost"}
|
|
282
|
+
else
|
|
283
|
+
{size: parts.join(",")}
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
|
|
119
288
|
# ── mj-section ─────────────────────────────────────────────────────────
|
|
120
289
|
|
|
121
290
|
def render_section(node, context, attrs)
|
|
@@ -124,6 +293,7 @@ module MjmlRb
|
|
|
124
293
|
css_class = a["css-class"]
|
|
125
294
|
bg_color = a["background-color"]
|
|
126
295
|
border_radius = a["border-radius"]
|
|
296
|
+
bg_has = has_background?(a)
|
|
127
297
|
|
|
128
298
|
# Box width: container minus horizontal padding and borders
|
|
129
299
|
border_left = parse_border_width(a["border-left"] || a["border"])
|
|
@@ -148,46 +318,85 @@ module MjmlRb
|
|
|
148
318
|
|
|
149
319
|
render_before = %(<!--[if mso | IE]><table#{outlook_attrs(before_pairs)}><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->)
|
|
150
320
|
|
|
151
|
-
# Section div, table, td
|
|
152
|
-
div_style = style_join(
|
|
153
|
-
"background" => bg_color,
|
|
154
|
-
"background-color" => bg_color,
|
|
155
|
-
"margin" => "0px auto",
|
|
156
|
-
"max-width" => "#{container_px}px"
|
|
157
|
-
)
|
|
158
|
-
|
|
321
|
+
# Section div, table, td — styles differ based on background-url presence
|
|
159
322
|
border_val = a["border"]
|
|
160
323
|
border_val = nil if border_val.nil? || border_val.to_s.strip.empty? || border_val.to_s.strip == "none"
|
|
161
324
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
325
|
+
if bg_has
|
|
326
|
+
bg_value = get_background(a)
|
|
327
|
+
bg_string = get_background_string(a)
|
|
328
|
+
bg_repeat = a["background-repeat"]
|
|
329
|
+
bg_size = a["background-size"]
|
|
330
|
+
|
|
331
|
+
div_style = style_join(
|
|
332
|
+
"background" => bg_value,
|
|
333
|
+
"background-position" => bg_string,
|
|
334
|
+
"background-repeat" => bg_repeat,
|
|
335
|
+
"background-size" => bg_size,
|
|
336
|
+
"margin" => "0px auto",
|
|
337
|
+
"max-width" => "#{container_px}px"
|
|
338
|
+
)
|
|
339
|
+
table_style = style_join(
|
|
340
|
+
"background" => bg_value,
|
|
341
|
+
"background-position" => bg_string,
|
|
342
|
+
"background-repeat" => bg_repeat,
|
|
343
|
+
"background-size" => bg_size,
|
|
344
|
+
"border-radius" => border_radius,
|
|
345
|
+
"width" => "100%"
|
|
346
|
+
)
|
|
347
|
+
td_style = style_join(
|
|
348
|
+
"border" => border_val,
|
|
349
|
+
"border-top" => a["border-top"],
|
|
350
|
+
"border-right" => a["border-right"],
|
|
351
|
+
"border-bottom" => a["border-bottom"],
|
|
352
|
+
"border-left" => a["border-left"],
|
|
353
|
+
"border-radius" => border_radius,
|
|
354
|
+
"direction" => a["direction"],
|
|
355
|
+
"font-size" => "0px",
|
|
356
|
+
"padding" => a["padding"],
|
|
357
|
+
"padding-top" => a["padding-top"],
|
|
358
|
+
"padding-right" => a["padding-right"],
|
|
359
|
+
"padding-bottom" => a["padding-bottom"],
|
|
360
|
+
"padding-left" => a["padding-left"],
|
|
361
|
+
"text-align" => a["text-align"]
|
|
362
|
+
)
|
|
363
|
+
else
|
|
364
|
+
div_style = style_join(
|
|
365
|
+
"background" => bg_color,
|
|
366
|
+
"background-color" => bg_color,
|
|
367
|
+
"margin" => "0px auto",
|
|
368
|
+
"max-width" => "#{container_px}px"
|
|
369
|
+
)
|
|
370
|
+
table_style = style_join(
|
|
371
|
+
"background" => bg_color,
|
|
372
|
+
"background-color" => bg_color,
|
|
373
|
+
"border-radius" => border_radius,
|
|
374
|
+
"width" => "100%"
|
|
375
|
+
)
|
|
376
|
+
td_style = style_join(
|
|
377
|
+
"border" => border_val,
|
|
378
|
+
"border-top" => a["border-top"],
|
|
379
|
+
"border-right" => a["border-right"],
|
|
380
|
+
"border-bottom" => a["border-bottom"],
|
|
381
|
+
"border-left" => a["border-left"],
|
|
382
|
+
"border-radius" => border_radius,
|
|
383
|
+
"background" => bg_color,
|
|
384
|
+
"background-color" => bg_color,
|
|
385
|
+
"direction" => a["direction"],
|
|
386
|
+
"font-size" => "0px",
|
|
387
|
+
"padding" => a["padding"],
|
|
388
|
+
"padding-top" => a["padding-top"],
|
|
389
|
+
"padding-right" => a["padding-right"],
|
|
390
|
+
"padding-bottom" => a["padding-bottom"],
|
|
391
|
+
"padding-left" => a["padding-left"],
|
|
392
|
+
"text-align" => a["text-align"]
|
|
393
|
+
)
|
|
394
|
+
end
|
|
187
395
|
|
|
188
396
|
div_attrs = {"class" => css_class, "style" => div_style}
|
|
189
397
|
table_attrs = {
|
|
190
398
|
"align" => "center",
|
|
399
|
+
"background" => bg_has ? a["background-url"] : nil,
|
|
191
400
|
"border" => "0",
|
|
192
401
|
"cellpadding" => "0",
|
|
193
402
|
"cellspacing" => "0",
|
|
@@ -202,14 +411,19 @@ module MjmlRb
|
|
|
202
411
|
}
|
|
203
412
|
inner = merge_outlook_conditionals(render_section_columns(node, context, box_width))
|
|
204
413
|
|
|
414
|
+
# Wrap in innerDiv when background image is present (prevents Yahoo whitespace gaps)
|
|
415
|
+
inner_content = bg_has ? %(<div style="line-height:0;font-size:0">#{inner}</div>) : inner
|
|
416
|
+
|
|
205
417
|
section_html =
|
|
206
418
|
%(<div#{html_attrs(div_attrs)}>) +
|
|
207
419
|
%(<table#{html_attrs(table_attrs)}>) +
|
|
208
|
-
%(<tbody><tr><td#{html_attrs(td_attrs)}>#{
|
|
420
|
+
%(<tbody><tr><td#{html_attrs(td_attrs)}>#{inner_content}</td></tr></tbody></table></div>)
|
|
209
421
|
|
|
210
422
|
render_after = %(<!--[if mso | IE]></td></tr></table><![endif]-->)
|
|
211
423
|
|
|
212
|
-
|
|
424
|
+
body = bg_has ? render_with_background(section_html, a, container_px) : section_html
|
|
425
|
+
|
|
426
|
+
"#{render_before}\n#{body}\n#{render_after}"
|
|
213
427
|
end
|
|
214
428
|
|
|
215
429
|
# Generate Outlook IE conditional wrappers around each column/group.
|
data/lib/mjml-rb/version.rb
CHANGED