mjml-rb 0.2.13 → 0.2.15
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/README.md +1 -1
- data/lib/mjml-rb/components/group.rb +134 -0
- data/lib/mjml-rb/components/section.rb +2 -6
- data/lib/mjml-rb/renderer.rb +35 -24
- data/lib/mjml-rb/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 990083fd9e656cfe50b74937926c14f37ea359adf49530e5ffbacf17ef0ea00a
|
|
4
|
+
data.tar.gz: a03cc468ddcdb25fdde896f3d53bbd8febaad32db0b3aae3ccad69725f11920c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ee2bff6d7a36be6fe4f95b19eefd60e9ff295d62d681cd7e3131ff9e298859b66fea7f5d6887ae3f984d9f13fc6aeda4f3f58b8ee6e6cfd9b603d804cb0305a2
|
|
7
|
+
data.tar.gz: fdd623a60697cb966591bfcad9fc973e71586ffc3776f6f3de5944fc63c2ea413324b3b6ca288303117b58fbfc12c8e2b13aca174b657a5e8c4a33eb73138e97
|
data/README.md
CHANGED
|
@@ -44,7 +44,7 @@ The table below tracks current JS-to-Ruby migration status for MJML components i
|
|
|
44
44
|
| `mj-section` | migrated | Implemented in `section.rb`. |
|
|
45
45
|
| `mj-wrapper` | migrated | Implemented via `section.rb`. |
|
|
46
46
|
| `mj-column` | migrated | Implemented in `column.rb`. |
|
|
47
|
-
| `mj-group` | migrated |
|
|
47
|
+
| `mj-group` | migrated | Implemented in `group.rb`, including width-aware child rendering and Outlook table wrappers. |
|
|
48
48
|
| `mj-text` | migrated | Implemented in `text.rb`. |
|
|
49
49
|
| `mj-image` | migrated | Implemented in `image.rb`. |
|
|
50
50
|
| `mj-button` | migrated | Implemented in `button.rb`. |
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
require_relative "base"
|
|
2
|
+
|
|
3
|
+
module MjmlRb
|
|
4
|
+
module Components
|
|
5
|
+
class Group < Base
|
|
6
|
+
TAGS = ["mj-group"].freeze
|
|
7
|
+
|
|
8
|
+
ALLOWED_ATTRIBUTES = {
|
|
9
|
+
"background-color" => "color",
|
|
10
|
+
"direction" => "enum(ltr,rtl)",
|
|
11
|
+
"vertical-align" => "enum(top,bottom,middle)",
|
|
12
|
+
"width" => "unit(px,%)"
|
|
13
|
+
}.freeze
|
|
14
|
+
|
|
15
|
+
DEFAULT_ATTRIBUTES = {
|
|
16
|
+
"direction" => "ltr"
|
|
17
|
+
}.freeze
|
|
18
|
+
|
|
19
|
+
def tags
|
|
20
|
+
TAGS
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def render(tag_name:, node:, context:, attrs:, parent:)
|
|
24
|
+
width_pct = context.delete(:_column_width_pct) || 100.0
|
|
25
|
+
a = DEFAULT_ATTRIBUTES.merge(attrs)
|
|
26
|
+
css_class = a["css-class"]
|
|
27
|
+
|
|
28
|
+
pct_str = width_pct.to_f.to_s.sub(/\.?0+$/, "")
|
|
29
|
+
class_suffix = pct_str.gsub(".", "-")
|
|
30
|
+
context[:column_widths][class_suffix] = pct_str if context[:column_widths]
|
|
31
|
+
|
|
32
|
+
group_class = "mj-column-per-#{class_suffix} mj-outlook-group-fix"
|
|
33
|
+
group_class = "#{group_class} #{css_class}" if css_class && !css_class.empty?
|
|
34
|
+
|
|
35
|
+
group_width = group_container_width(context, a, width_pct)
|
|
36
|
+
div_style = style_join(
|
|
37
|
+
"font-size" => "0",
|
|
38
|
+
"line-height" => "0",
|
|
39
|
+
"text-align" => "left",
|
|
40
|
+
"display" => "inline-block",
|
|
41
|
+
"width" => "100%",
|
|
42
|
+
"direction" => a["direction"],
|
|
43
|
+
"vertical-align" => a["vertical-align"],
|
|
44
|
+
"background-color" => a["background-color"]
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
inner = with_group_container_width(context, group_width) do
|
|
48
|
+
render_group_children(node, context, a, group_width)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
%(<div class="#{escape_attr(group_class)}" style="#{div_style}">#{inner}</div>)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def render_group_children(node, context, attrs, group_width)
|
|
57
|
+
columns = node.element_children.select { |child| child.tag_name == "mj-column" }
|
|
58
|
+
widths = renderer.send(:compute_column_widths, columns, context)
|
|
59
|
+
group_width_px = parse_pixel_value(group_width)
|
|
60
|
+
group_bg = attrs["background-color"]
|
|
61
|
+
table_attrs = {
|
|
62
|
+
"bgcolor" => (group_bg == "none" ? nil : group_bg),
|
|
63
|
+
"border" => "0",
|
|
64
|
+
"cellpadding" => "0",
|
|
65
|
+
"cellspacing" => "0",
|
|
66
|
+
"role" => "presentation"
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
open_table = %(<!--[if mso | IE]><table#{html_attrs(table_attrs)}><tr><![endif]-->)
|
|
70
|
+
close_table = %(<!--[if mso | IE]></tr></table><![endif]-->)
|
|
71
|
+
column_index = 0
|
|
72
|
+
|
|
73
|
+
body = with_inherited_mj_class(context, node) do
|
|
74
|
+
node.children.map do |child|
|
|
75
|
+
case child.tag_name
|
|
76
|
+
when "mj-column"
|
|
77
|
+
width_pct = widths[column_index] || 100.0
|
|
78
|
+
column_index += 1
|
|
79
|
+
context[:_column_width_pct] = width_pct
|
|
80
|
+
td_style = style_join(
|
|
81
|
+
"vertical-align" => resolved_attributes(child, context)["vertical-align"] || "top",
|
|
82
|
+
"width" => "#{(group_width_px * width_pct / 100.0).round}px"
|
|
83
|
+
)
|
|
84
|
+
td_open = %(<!--[if mso | IE]><td#{html_attrs("style" => td_style)}><![endif]-->)
|
|
85
|
+
td_close = %(<!--[if mso | IE]></td><![endif]-->)
|
|
86
|
+
"#{td_open}#{render_node(child, context, parent: "mj-group")}#{td_close}"
|
|
87
|
+
when "mj-raw"
|
|
88
|
+
render_node(child, context, parent: "mj-group")
|
|
89
|
+
else
|
|
90
|
+
render_node(child, context, parent: "mj-group")
|
|
91
|
+
end
|
|
92
|
+
end.join("\n")
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
"#{open_table}#{body}#{close_table}"
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def with_group_container_width(context, width)
|
|
99
|
+
previous = context[:container_width]
|
|
100
|
+
context[:container_width] = width
|
|
101
|
+
yield
|
|
102
|
+
ensure
|
|
103
|
+
context[:container_width] = previous
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def group_container_width(context, attrs, width_pct)
|
|
107
|
+
parent_width = parse_pixel_value(context[:container_width] || "600px")
|
|
108
|
+
width = attrs["width"]
|
|
109
|
+
|
|
110
|
+
raw_width =
|
|
111
|
+
if present_attr?(width) && width.end_with?("%")
|
|
112
|
+
parent_width * parse_pixel_value(width) / 100.0
|
|
113
|
+
elsif present_attr?(width) && width.end_with?("px")
|
|
114
|
+
parse_pixel_value(width)
|
|
115
|
+
else
|
|
116
|
+
parent_width * width_pct / 100.0
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
"#{[raw_width, 0].max}px"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def present_attr?(value)
|
|
123
|
+
value && !value.empty?
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def parse_pixel_value(value)
|
|
127
|
+
return 0.0 unless present_attr?(value)
|
|
128
|
+
|
|
129
|
+
matched = value.to_s.match(/(-?\d+(?:\.\d+)?)/)
|
|
130
|
+
matched ? matched[1].to_f : 0.0
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
@@ -233,12 +233,8 @@ module MjmlRb
|
|
|
233
233
|
td_open = %(<!--[if mso | IE]><td class="" style="vertical-align:#{v_align};width:#{col_px}px;" ><![endif]-->)
|
|
234
234
|
td_close = %(<!--[if mso | IE]></td><![endif]-->)
|
|
235
235
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
else
|
|
239
|
-
context[:_column_width_pct] = widths[i]
|
|
240
|
-
render_node(col, context, parent: "mj-section")
|
|
241
|
-
end
|
|
236
|
+
context[:_column_width_pct] = widths[i]
|
|
237
|
+
col_html = render_node(col, context, parent: "mj-section")
|
|
242
238
|
|
|
243
239
|
"#{td_open}\n#{col_html}\n#{td_close}"
|
|
244
240
|
end
|
data/lib/mjml-rb/renderer.rb
CHANGED
|
@@ -7,6 +7,7 @@ require_relative "components/breakpoint"
|
|
|
7
7
|
require_relative "components/button"
|
|
8
8
|
require_relative "components/carousel"
|
|
9
9
|
require_relative "components/carousel_image"
|
|
10
|
+
require_relative "components/group"
|
|
10
11
|
require_relative "components/head"
|
|
11
12
|
require_relative "components/hero"
|
|
12
13
|
require_relative "components/image"
|
|
@@ -163,24 +164,7 @@ module MjmlRb
|
|
|
163
164
|
if (component = component_for(node.tag_name))
|
|
164
165
|
return component.render(tag_name: node.tag_name, node: node, context: context, attrs: attrs, parent: parent)
|
|
165
166
|
end
|
|
166
|
-
|
|
167
|
-
case node.tag_name
|
|
168
|
-
when "mj-group"
|
|
169
|
-
render_group(node, context)
|
|
170
|
-
else
|
|
171
|
-
render_children(node, context, parent: node.tag_name)
|
|
172
|
-
end
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
def render_group(node, context, width_pct = 100)
|
|
176
|
-
items = node.element_children.select { |e| e.tag_name == "mj-column" }
|
|
177
|
-
widths = compute_column_widths(items, context)
|
|
178
|
-
with_inherited_mj_class(context, node) do
|
|
179
|
-
items.each_with_index.map do |item, i|
|
|
180
|
-
context[:_column_width_pct] = widths[i]
|
|
181
|
-
render_node(item, context, parent: "mj-group")
|
|
182
|
-
end.join("\n")
|
|
183
|
-
end
|
|
167
|
+
render_children(node, context, parent: node.tag_name)
|
|
184
168
|
end
|
|
185
169
|
|
|
186
170
|
def compute_column_widths(columns, context)
|
|
@@ -360,25 +344,32 @@ module MjmlRb
|
|
|
360
344
|
property, value = entry.split(":", 2).map { |part| part&.strip }
|
|
361
345
|
next if property.nil? || property.empty? || value.nil? || value.empty?
|
|
362
346
|
|
|
363
|
-
|
|
347
|
+
important = value.match?(/\s*!important\s*\z/)
|
|
348
|
+
memo[property] = {
|
|
349
|
+
value: value.sub(/\s*!important\s*\z/, "").strip,
|
|
350
|
+
important: important
|
|
351
|
+
}
|
|
364
352
|
end
|
|
365
353
|
end
|
|
366
354
|
|
|
367
355
|
def merge_inline_style!(node, declarations)
|
|
368
356
|
existing = parse_css_declarations(node["style"].to_s)
|
|
369
357
|
declarations.each do |property, value|
|
|
370
|
-
existing[property] = value
|
|
358
|
+
existing[property] = merge_css_declaration(existing[property], value)
|
|
371
359
|
end
|
|
372
360
|
normalize_background_fallbacks!(node, existing)
|
|
373
|
-
node["style"] = existing
|
|
361
|
+
node["style"] = serialize_css_declarations(existing)
|
|
374
362
|
end
|
|
375
363
|
|
|
376
364
|
def normalize_background_fallbacks!(node, declarations)
|
|
377
|
-
background_color = declarations["background-color"]
|
|
365
|
+
background_color = declaration_value(declarations["background-color"])
|
|
378
366
|
return if background_color.nil? || background_color.empty?
|
|
379
367
|
|
|
380
|
-
if syncable_background?(declarations["background"])
|
|
381
|
-
declarations["background"] =
|
|
368
|
+
if syncable_background?(declaration_value(declarations["background"]))
|
|
369
|
+
declarations["background"] = {
|
|
370
|
+
value: background_color,
|
|
371
|
+
important: declarations.fetch("background-color", {}).fetch(:important, false)
|
|
372
|
+
}
|
|
382
373
|
end
|
|
383
374
|
|
|
384
375
|
return unless node.name == "td"
|
|
@@ -406,6 +397,25 @@ module MjmlRb
|
|
|
406
397
|
!normalized.include?(" right")
|
|
407
398
|
end
|
|
408
399
|
|
|
400
|
+
def merge_css_declaration(existing, incoming)
|
|
401
|
+
return incoming if existing.nil?
|
|
402
|
+
return existing if existing[:important] && !incoming[:important]
|
|
403
|
+
|
|
404
|
+
incoming
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
def declaration_value(declaration)
|
|
408
|
+
declaration && declaration[:value]
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
def serialize_css_declarations(declarations)
|
|
412
|
+
declarations.map do |property, declaration|
|
|
413
|
+
value = declaration[:value]
|
|
414
|
+
value = "#{value} !important" if declaration[:important]
|
|
415
|
+
"#{property}: #{value}"
|
|
416
|
+
end.join("; ")
|
|
417
|
+
end
|
|
418
|
+
|
|
409
419
|
def append_component_head_styles(document, context)
|
|
410
420
|
component_registry.each_value.uniq.each do |component|
|
|
411
421
|
next unless component.respond_to?(:head_style)
|
|
@@ -444,6 +454,7 @@ module MjmlRb
|
|
|
444
454
|
register_component(registry, Components::Button.new(self))
|
|
445
455
|
register_component(registry, Components::Carousel.new(self))
|
|
446
456
|
register_component(registry, Components::CarouselImage.new(self))
|
|
457
|
+
register_component(registry, Components::Group.new(self))
|
|
447
458
|
register_component(registry, Components::Hero.new(self))
|
|
448
459
|
register_component(registry, Components::Image.new(self))
|
|
449
460
|
register_component(registry, Components::Navbar.new(self))
|
data/lib/mjml-rb/version.rb
CHANGED
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.
|
|
4
|
+
version: 0.2.15
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrei Andriichuk
|
|
@@ -63,6 +63,7 @@ files:
|
|
|
63
63
|
- lib/mjml-rb/components/carousel_image.rb
|
|
64
64
|
- lib/mjml-rb/components/column.rb
|
|
65
65
|
- lib/mjml-rb/components/divider.rb
|
|
66
|
+
- lib/mjml-rb/components/group.rb
|
|
66
67
|
- lib/mjml-rb/components/head.rb
|
|
67
68
|
- lib/mjml-rb/components/hero.rb
|
|
68
69
|
- lib/mjml-rb/components/html_attributes.rb
|