mjml-rb 0.2.18 → 0.2.20

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
  SHA256:
3
- metadata.gz: b82bc910ae0c3ba34cd905a28e8d2f52e349d7a1b099981eb576592a3274ba5b
4
- data.tar.gz: 6e8ffbc112105cb1d11b5ba46f7b7a44bd9377c52498edc34e97c1ee421d4ac2
3
+ metadata.gz: 97ca7a3d968fdd4dee9f34ef7806283cb1a53fda0810dcf2456770f298831b35
4
+ data.tar.gz: 53a50ac16e06c719fa6533de98e43c35233a1ab0470bf81c366c1be62726c2b4
5
5
  SHA512:
6
- metadata.gz: 2eb6640d8405e3f8297a8fbd081530a8565fed4d410b297b78443d93bb1640db84d611aec0746beab99b5183a41d1d7b24d658ea048a2806f4899175c613bc95
7
- data.tar.gz: 5f29c238409b28d5048e1a7b85aff199f93a975afcbf4d59f7d5562c9abadf3c4c2eb5e0768bb3e2da14e976bb9ad99e7743ddc6f3325f6820d5e06c63f0a414
6
+ metadata.gz: 22bb47ef2611887dbf6a19ee38cfb7da366060b632bf3cfb9ea548798a29e120287ae4de035255560d36dc5c407eeef3c04a007695b297da9230ecbd17d0dcb1
7
+ data.tar.gz: f9f14bde5dd817a322e40da7b0de8645185c6b25e884d9d3aa45df7f3a5fca94eb92723204f4a193fc696bfe93f1b31a2033bf99042a3637adc7d0d61aa65343
@@ -16,27 +16,29 @@ module MjmlRb
16
16
  "border" => "string",
17
17
  "border-bottom" => "string",
18
18
  "border-left" => "string",
19
- "border-radius" => "unit(px,%){1,4}",
19
+ "border-radius" => "string",
20
20
  "border-right" => "string",
21
21
  "border-top" => "string",
22
22
  "direction" => "enum(ltr,rtl)",
23
+ "full-width" => "enum(full-width,false,)",
23
24
  "padding" => "unit(px,%){1,4}",
24
25
  "padding-bottom" => "unit(px,%)",
25
26
  "padding-left" => "unit(px,%)",
26
27
  "padding-right" => "unit(px,%)",
27
28
  "padding-top" => "unit(px,%)",
28
- "text-align" => "enum(left,center,right)"
29
+ "text-align" => "enum(left,center,right)",
30
+ "text-padding" => "unit(px,%){1,4}"
29
31
  }.freeze
30
32
 
31
33
  WRAPPER_ALLOWED_ATTRIBUTES = SECTION_ALLOWED_ATTRIBUTES.merge(
32
34
  "gap" => "unit(px)",
33
- "full-width" => "enum(full-width)"
34
35
  ).freeze
35
36
 
36
37
  DEFAULT_ATTRIBUTES = {
37
38
  "direction" => "ltr",
38
39
  "padding" => "20px 0",
39
40
  "text-align" => "center",
41
+ "text-padding" => "4px 4px 4px 0",
40
42
  "background-repeat" => "repeat",
41
43
  "background-size" => "auto",
42
44
  "background-position" => "top center"
@@ -61,7 +63,12 @@ module MjmlRb
61
63
  when "mj-wrapper"
62
64
  render_wrapper(node, context, attrs)
63
65
  else
64
- render_section(node, context, attrs)
66
+ a = self.class.default_attributes.merge(attrs)
67
+ if a["full-width"] == "full-width"
68
+ render_full_width_section(node, context, a)
69
+ else
70
+ render_section(node, context, a)
71
+ end
65
72
  end
66
73
  end
67
74
 
@@ -112,6 +119,10 @@ module MjmlRb
112
119
  end
113
120
  end
114
121
 
122
+ def has_border_radius?(value)
123
+ value && !value.to_s.strip.empty?
124
+ end
125
+
115
126
  # Merge adjacent Outlook conditional comments. Applied locally within
116
127
  # each section/wrapper to avoid incorrectly merging across sibling sections.
117
128
  def merge_outlook_conditionals(html)
@@ -205,7 +216,7 @@ module MjmlRb
205
216
  "right" => "100%", "bottom" => "100%"
206
217
  }.freeze
207
218
 
208
- def render_with_background(section_html, a, container_px)
219
+ def render_with_background(section_html, a, container_px, full_width: false)
209
220
  bg_url = a["background-url"]
210
221
  bg_color = a["background-color"]
211
222
  bg_repeat = a["background-repeat"] || "repeat"
@@ -247,7 +258,9 @@ module MjmlRb
247
258
  fill_pairs << ["aspect", v_size_attrs[:aspect]] if v_size_attrs[:aspect]
248
259
  fill_str = fill_pairs.map { |(k, v)| %(#{k}="#{escape_attr(v.to_s)}") }.join(" ")
249
260
 
250
- %(<!--[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]-->) +
261
+ rect_style = full_width ? "mso-width-percent:1000;" : "width:#{container_px}px;"
262
+
263
+ %(<!--[if mso | IE]><v:rect style="#{rect_style}" 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]-->) +
251
264
  section_html +
252
265
  %(<!--[if mso | IE]></v:textbox></v:rect><![endif]-->)
253
266
  end
@@ -288,8 +301,7 @@ module MjmlRb
288
301
 
289
302
  # ── mj-section ─────────────────────────────────────────────────────────
290
303
 
291
- def render_section(node, context, attrs)
292
- a = self.class.default_attributes.merge(attrs)
304
+ def render_section(node, context, a)
293
305
  container_px = parse_px(context[:container_width] || "600px")
294
306
  css_class = a["css-class"]
295
307
  bg_color = a["background-color"]
@@ -304,7 +316,78 @@ module MjmlRb
304
316
  pad_right = parse_padding_side(a, "right")
305
317
  box_width = container_px - pad_left - pad_right - border_left - border_right
306
318
 
307
- # renderBefore Outlook outer wrapper table
319
+ render_before = render_section_before(css_class, container_px, bg_color, wrapper_gap)
320
+
321
+ section_html = build_section_html(
322
+ node,
323
+ context,
324
+ a,
325
+ container_px: container_px,
326
+ box_width: box_width,
327
+ css_class: css_class,
328
+ bg_color: bg_color,
329
+ border_radius: border_radius,
330
+ bg_has: bg_has,
331
+ wrapper_gap: wrapper_gap,
332
+ full_width: false
333
+ )
334
+
335
+ render_after = %(<!--[if mso | IE]></td></tr></table><![endif]-->)
336
+
337
+ body = bg_has ? render_with_background(section_html, a, container_px) : section_html
338
+
339
+ "#{render_before}\n#{body}\n#{render_after}"
340
+ end
341
+
342
+ def render_full_width_section(node, context, a)
343
+ container_px = parse_px(context[:container_width] || "600px")
344
+ css_class = a["css-class"]
345
+ bg_color = a["background-color"]
346
+ border_radius = a["border-radius"]
347
+ bg_has = has_background?(a)
348
+ wrapper_gap = context[:_wrapper_child_gap]
349
+
350
+ border_left = parse_border_width(a["border-left"] || a["border"])
351
+ border_right = parse_border_width(a["border-right"] || a["border"])
352
+ pad_left = parse_padding_side(a, "left")
353
+ pad_right = parse_padding_side(a, "right")
354
+ box_width = container_px - pad_left - pad_right - border_left - border_right
355
+
356
+ render_before = render_section_before(css_class, container_px, bg_color, wrapper_gap)
357
+ section_html = build_section_html(
358
+ node,
359
+ context,
360
+ a,
361
+ container_px: container_px,
362
+ box_width: box_width,
363
+ css_class: nil,
364
+ bg_color: bg_color,
365
+ border_radius: border_radius,
366
+ bg_has: bg_has,
367
+ wrapper_gap: wrapper_gap,
368
+ full_width: true
369
+ )
370
+ render_after = %(<!--[if mso | IE]></td></tr></table><![endif]-->)
371
+
372
+ inner = "#{render_before}\n#{section_html}\n#{render_after}"
373
+ body = bg_has ? render_with_background(inner, a, container_px, full_width: true) : inner
374
+
375
+ outer_style = full_width_table_style(a)
376
+ outer_attrs = {
377
+ "align" => "center",
378
+ "class" => css_class,
379
+ "background" => bg_has ? a["background-url"] : nil,
380
+ "border" => "0",
381
+ "cellpadding" => "0",
382
+ "cellspacing" => "0",
383
+ "role" => "presentation",
384
+ "style" => outer_style
385
+ }
386
+
387
+ %(<table#{html_attrs(outer_attrs)}><tbody><tr><td>#{body}</td></tr></tbody></table>)
388
+ end
389
+
390
+ def render_section_before(css_class, container_px, bg_color, wrapper_gap)
308
391
  outlook_class = css_class ? "#{css_class}-outlook" : ""
309
392
  before_pairs = [
310
393
  ["align", "center"],
@@ -318,89 +401,68 @@ module MjmlRb
318
401
  ]
319
402
  before_pairs << ["bgcolor", bg_color] if bg_color
320
403
 
321
- 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]-->)
404
+ %(<!--[if mso | IE]><table#{outlook_attrs(before_pairs)}><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->)
405
+ end
322
406
 
323
- # Section div, table, td styles differ based on background-url presence
407
+ def build_section_html(node, context, a, container_px:, box_width:, css_class:, bg_color:, border_radius:, bg_has:, wrapper_gap:, full_width:)
324
408
  border_val = a["border"]
325
409
  border_val = nil if border_val.nil? || border_val.to_s.strip.empty? || border_val.to_s.strip == "none"
410
+ has_border_radius = has_border_radius?(border_radius)
411
+
412
+ background_styles = if bg_has
413
+ bg_value = get_background(a)
414
+ {
415
+ "background" => bg_value,
416
+ "background-position" => get_background_string(a),
417
+ "background-repeat" => a["background-repeat"],
418
+ "background-size" => a["background-size"]
419
+ }
420
+ else
421
+ {
422
+ "background" => bg_color,
423
+ "background-color" => bg_color
424
+ }
425
+ end
326
426
 
327
- if bg_has
328
- bg_value = get_background(a)
329
- bg_string = get_background_string(a)
330
- bg_repeat = a["background-repeat"]
331
- bg_size = a["background-size"]
332
-
333
- div_style = style_join(
334
- "background" => bg_value,
335
- "background-position" => bg_string,
336
- "background-repeat" => bg_repeat,
337
- "background-size" => bg_size,
338
- "margin" => "0px auto",
339
- "margin-top" => wrapper_gap,
340
- "max-width" => "#{container_px}px"
341
- )
342
- table_style = style_join(
343
- "background" => bg_value,
344
- "background-position" => bg_string,
345
- "background-repeat" => bg_repeat,
346
- "background-size" => bg_size,
347
- "border-radius" => border_radius,
348
- "width" => "100%"
349
- )
350
- td_style = style_join(
351
- "border" => border_val,
352
- "border-top" => a["border-top"],
353
- "border-right" => a["border-right"],
354
- "border-bottom" => a["border-bottom"],
355
- "border-left" => a["border-left"],
356
- "border-radius" => border_radius,
357
- "direction" => a["direction"],
358
- "font-size" => "0px",
359
- "padding" => a["padding"],
360
- "padding-top" => a["padding-top"],
361
- "padding-right" => a["padding-right"],
362
- "padding-bottom" => a["padding-bottom"],
363
- "padding-left" => a["padding-left"],
364
- "text-align" => a["text-align"]
365
- )
366
- else
367
- div_style = style_join(
368
- "background" => bg_color,
369
- "background-color" => bg_color,
370
- "margin" => "0px auto",
371
- "margin-top" => wrapper_gap,
372
- "max-width" => "#{container_px}px"
373
- )
374
- table_style = style_join(
375
- "background" => bg_color,
376
- "background-color" => bg_color,
377
- "border-radius" => border_radius,
378
- "width" => "100%"
379
- )
380
- td_style = style_join(
381
- "border" => border_val,
382
- "border-top" => a["border-top"],
383
- "border-right" => a["border-right"],
384
- "border-bottom" => a["border-bottom"],
385
- "border-left" => a["border-left"],
386
- "border-radius" => border_radius,
387
- "background" => bg_color,
388
- "background-color" => bg_color,
389
- "direction" => a["direction"],
390
- "font-size" => "0px",
391
- "padding" => a["padding"],
392
- "padding-top" => a["padding-top"],
393
- "padding-right" => a["padding-right"],
394
- "padding-bottom" => a["padding-bottom"],
395
- "padding-left" => a["padding-left"],
396
- "text-align" => a["text-align"]
397
- )
398
- end
427
+ div_style = style_join(
428
+ {
429
+ "border-radius" => border_radius,
430
+ "overflow" => (has_border_radius ? "hidden" : nil),
431
+ "margin" => "0px auto",
432
+ "margin-top" => wrapper_gap,
433
+ "max-width" => "#{container_px}px"
434
+ }.merge(full_width ? {} : background_styles)
435
+ )
436
+ table_style = style_join(
437
+ {
438
+ "border-radius" => border_radius,
439
+ "border-collapse" => (has_border_radius ? "separate" : nil),
440
+ "width" => "100%"
441
+ }.merge(full_width ? {} : background_styles)
442
+ )
443
+ td_style = style_join(
444
+ "border" => border_val,
445
+ "border-top" => a["border-top"],
446
+ "border-right" => a["border-right"],
447
+ "border-bottom" => a["border-bottom"],
448
+ "border-left" => a["border-left"],
449
+ "border-radius" => border_radius,
450
+ "background" => (full_width ? nil : bg_color),
451
+ "background-color" => (full_width ? nil : bg_color),
452
+ "direction" => a["direction"],
453
+ "font-size" => "0px",
454
+ "padding" => a["padding"],
455
+ "padding-top" => a["padding-top"],
456
+ "padding-right" => a["padding-right"],
457
+ "padding-bottom" => a["padding-bottom"],
458
+ "padding-left" => a["padding-left"],
459
+ "text-align" => a["text-align"]
460
+ )
399
461
 
400
462
  div_attrs = {"class" => css_class, "style" => div_style}
401
463
  table_attrs = {
402
464
  "align" => "center",
403
- "background" => bg_has ? a["background-url"] : nil,
465
+ "background" => (bg_has && !full_width ? a["background-url"] : nil),
404
466
  "border" => "0",
405
467
  "cellpadding" => "0",
406
468
  "cellspacing" => "0",
@@ -414,20 +476,29 @@ module MjmlRb
414
476
  "style" => td_style
415
477
  }
416
478
  inner = merge_outlook_conditionals(render_section_columns(node, context, box_width))
417
-
418
- # Wrap in innerDiv when background image is present (prevents Yahoo whitespace gaps)
419
479
  inner_content = bg_has ? %(<div style="line-height:0;font-size:0">#{inner}</div>) : inner
420
480
 
421
- section_html =
422
- %(<div#{html_attrs(div_attrs)}>) +
423
- %(<table#{html_attrs(table_attrs)}>) +
424
- %(<tbody><tr><td#{html_attrs(td_attrs)}>#{inner_content}</td></tr></tbody></table></div>)
481
+ %(<div#{html_attrs(div_attrs)}><table#{html_attrs(table_attrs)}><tbody><tr><td#{html_attrs(td_attrs)}>#{inner_content}</td></tr></tbody></table></div>)
482
+ end
425
483
 
426
- render_after = %(<!--[if mso | IE]></td></tr></table><![endif]-->)
484
+ def full_width_table_style(a)
485
+ style = {"width" => "100%"}
427
486
 
428
- body = bg_has ? render_with_background(section_html, a, container_px) : section_html
487
+ if has_background?(a)
488
+ style.merge!(
489
+ "background" => get_background(a),
490
+ "background-position" => get_background_string(a),
491
+ "background-repeat" => a["background-repeat"],
492
+ "background-size" => a["background-size"]
493
+ )
494
+ else
495
+ style.merge!(
496
+ "background" => a["background-color"],
497
+ "background-color" => a["background-color"]
498
+ )
499
+ end
429
500
 
430
- "#{render_before}\n#{body}\n#{render_after}"
501
+ style_join(style)
431
502
  end
432
503
 
433
504
  # Generate Outlook IE conditional wrappers around each column/group.
@@ -468,8 +539,10 @@ module MjmlRb
468
539
  container_px = parse_px(context[:container_width] || "600px")
469
540
  css_class = a["css-class"]
470
541
  bg_color = a["background-color"]
542
+ border_radius = a["border-radius"]
471
543
  full_width = a["full-width"] == "full-width"
472
544
  wrapper_gap = context[:_wrapper_child_gap]
545
+ has_border_radius = has_border_radius?(border_radius)
473
546
 
474
547
  # renderBefore — same structure as section
475
548
  outlook_class = css_class ? "#{css_class}-outlook" : ""
@@ -490,6 +563,8 @@ module MjmlRb
490
563
  div_style = style_join(
491
564
  "background" => bg_color,
492
565
  "background-color" => bg_color,
566
+ "border-radius" => border_radius,
567
+ "overflow" => (has_border_radius ? "hidden" : nil),
493
568
  "margin" => "0px auto",
494
569
  "margin-top" => wrapper_gap,
495
570
  "max-width" => (full_width ? nil : "#{container_px}px")
@@ -498,10 +573,13 @@ module MjmlRb
498
573
  table_style = style_join(
499
574
  "background" => bg_color,
500
575
  "background-color" => bg_color,
576
+ "border-radius" => border_radius,
577
+ "border-collapse" => (has_border_radius ? "separate" : nil),
501
578
  "width" => "100%"
502
579
  )
503
580
 
504
581
  td_style = style_join(
582
+ "border-radius" => border_radius,
505
583
  "direction" => a["direction"],
506
584
  "font-size" => "0px",
507
585
  "padding" => a["padding"],
@@ -5,6 +5,28 @@ module MjmlRb
5
5
  class Table < Base
6
6
  TAGS = ["mj-table"].freeze
7
7
 
8
+ ALLOWED_ATTRIBUTES = {
9
+ "align" => "enum(left,right,center)",
10
+ "border" => "string",
11
+ "cellpadding" => "string",
12
+ "cellspacing" => "string",
13
+ "color" => "color",
14
+ "container-background-color" => "color",
15
+ "font-family" => "string",
16
+ "font-size" => "string",
17
+ "font-weight" => "string",
18
+ "line-height" => "string",
19
+ "padding" => "unit(px,%){1,4}",
20
+ "padding-top" => "unit(px,%)",
21
+ "padding-right" => "unit(px,%)",
22
+ "padding-bottom" => "unit(px,%)",
23
+ "padding-left" => "unit(px,%)",
24
+ "role" => "string",
25
+ "table-layout" => "enum(auto,fixed)",
26
+ "vertical-align" => "enum(top,bottom,middle)",
27
+ "width" => "string"
28
+ }.freeze
29
+
8
30
  DEFAULTS = {
9
31
  "align" => "left",
10
32
  "border" => "none",
@@ -49,6 +71,7 @@ module MjmlRb
49
71
  "color" => a["color"],
50
72
  "font-family" => a["font-family"],
51
73
  "font-size" => a["font-size"],
74
+ "font-weight" => a["font-weight"],
52
75
  "line-height" => a["line-height"],
53
76
  "table-layout" => a["table-layout"],
54
77
  "width" => a["width"],
@@ -1,3 +1,3 @@
1
1
  module MjmlRb
2
- VERSION = "0.2.18".freeze
2
+ VERSION = "0.2.20".freeze
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mjml-rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.18
4
+ version: 0.2.20
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrei Andriichuk