mjml-rb 0.2.2 → 0.2.4
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 +4 -2
- data/lib/mjml-rb/components/accordion.rb +24 -20
- data/lib/mjml-rb/components/attributes.rb +53 -0
- data/lib/mjml-rb/components/base.rb +4 -0
- data/lib/mjml-rb/components/navbar.rb +10 -8
- data/lib/mjml-rb/components/section.rb +25 -21
- data/lib/mjml-rb/components/social.rb +17 -13
- data/lib/mjml-rb/parser.rb +15 -1
- data/lib/mjml-rb/renderer.rb +84 -22
- data/lib/mjml-rb/version.rb +1 -1
- data/mjml-rb.gemspec +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4bb6667d87d9376e60199a47e987e10cfb496be78c4bd549f71fa049c2618aff
|
|
4
|
+
data.tar.gz: 98f63a171ea6a2d5b1dd6212c86a13cdd2eabf71c1ab9f12d15f8c83a04e30d2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 33c9d050ef8ebd560b92a513391957c9fd2ebea43023d3977c5c06fe154bc7eef1bf6646abbf555dcbf6f53d4900461cceb89daaf0e3b2be36d3e08cec445b38
|
|
7
|
+
data.tar.gz: 839a5ddbe4448b2c86b9b015d59468e8f2553a78432cf6371bdb6d061c850568850bde0316fd9be6ac548a0bc17715900d233cd1538cff03785383f29d960240
|
data/README.md
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
> and not all components or attributes are fully implemented yet.
|
|
9
9
|
> **Do not use in production without thorough testing of every template against
|
|
10
10
|
> the official npm renderer.** API and output format may change without notice.
|
|
11
|
+
> This is a **fully open source project**, and help is welcome:
|
|
12
|
+
> feedback, bug reports, test cases, optimizations, proposals, and pull requests.
|
|
11
13
|
> No warranty of any kind is provided.
|
|
12
14
|
|
|
13
15
|
This directory contains a Ruby-first implementation of the main MJML user-facing tooling:
|
|
@@ -60,9 +62,9 @@ The table below tracks current JS-to-Ruby migration status for MJML components i
|
|
|
60
62
|
| `mj-navbar-link` | migrated | Implemented in `navbar.rb` as an ending-tag navbar child component. |
|
|
61
63
|
| `mj-raw` | migrated | Implemented in `raw.rb`, including head insertion and top-level `position="file-start"` output before the doctype. |
|
|
62
64
|
| `mj-head` | partial | Core tags such as `mj-title`, `mj-preview`, `mj-style`, `mj-font`, and `mj-attributes` are supported. |
|
|
63
|
-
| `mj-attributes` |
|
|
65
|
+
| `mj-attributes` | migrated | Implemented in `attributes.rb`, including npm-style `mj-class` descendant defaults. |
|
|
64
66
|
| `mj-all` | partial | Supported through `mj-attributes`. |
|
|
65
|
-
| `mj-class` |
|
|
67
|
+
| `mj-class` | migrated | Supported through `attributes.rb`, including nested per-tag descendant defaults. |
|
|
66
68
|
| `mj-title` | partial | Supported through head context. |
|
|
67
69
|
| `mj-preview` | partial | Supported through head context. |
|
|
68
70
|
| `mj-style` | partial | Supported, including inline CSS application. |
|
|
@@ -94,16 +94,18 @@ module MjmlRb
|
|
|
94
94
|
"border-bottom" => "none",
|
|
95
95
|
"font-family" => accordion_attrs["font-family"]
|
|
96
96
|
)
|
|
97
|
-
inner = node
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
97
|
+
inner = with_inherited_mj_class(context, node) do
|
|
98
|
+
node.element_children.map do |child|
|
|
99
|
+
case child.tag_name
|
|
100
|
+
when "mj-accordion-element"
|
|
101
|
+
render_accordion_element(child, context, accordion_attrs)
|
|
102
|
+
when "mj-raw"
|
|
103
|
+
raw_inner(child)
|
|
104
|
+
else
|
|
105
|
+
render_node(child, context, parent: "mj-accordion")
|
|
106
|
+
end
|
|
107
|
+
end.join
|
|
108
|
+
end
|
|
107
109
|
|
|
108
110
|
%(<tr><td style="#{outer_style}"><table role="presentation" width="100%" cellspacing="0" cellpadding="0" class="mj-accordion" style="#{table_style}"><tbody>#{inner}</tbody></table></td></tr>)
|
|
109
111
|
end
|
|
@@ -125,16 +127,18 @@ module MjmlRb
|
|
|
125
127
|
content = []
|
|
126
128
|
content << render_accordion_title(nil, attrs) unless has_title
|
|
127
129
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
130
|
+
with_inherited_mj_class(context, node) do
|
|
131
|
+
children.each do |child|
|
|
132
|
+
case child.tag_name
|
|
133
|
+
when "mj-accordion-title"
|
|
134
|
+
child_attrs = attrs.merge(resolved_attributes(child, context))
|
|
135
|
+
content << render_accordion_title(child, child_attrs)
|
|
136
|
+
when "mj-accordion-text"
|
|
137
|
+
child_attrs = attrs.merge(resolved_attributes(child, context))
|
|
138
|
+
content << render_accordion_text(child, child_attrs)
|
|
139
|
+
when "mj-raw"
|
|
140
|
+
content << raw_inner(child)
|
|
141
|
+
end
|
|
138
142
|
end
|
|
139
143
|
end
|
|
140
144
|
content << render_accordion_text(nil, attrs) unless has_text
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
require_relative "base"
|
|
2
|
+
|
|
3
|
+
module MjmlRb
|
|
4
|
+
module Components
|
|
5
|
+
class Attributes < Base
|
|
6
|
+
TAGS = %w[mj-attributes mj-all mj-class].freeze
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
def allowed_attributes_for(tag_name)
|
|
10
|
+
{}
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def allowed_attributes
|
|
14
|
+
{}
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def tags
|
|
19
|
+
TAGS
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def render(tag_name:, node:, context:, attrs:, parent:)
|
|
23
|
+
""
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def handle_head(attributes_node, context)
|
|
27
|
+
attributes_node.element_children.each do |child|
|
|
28
|
+
case child.tag_name
|
|
29
|
+
when "mj-all"
|
|
30
|
+
context[:global_defaults].merge!(child.attributes)
|
|
31
|
+
when "mj-class"
|
|
32
|
+
name = child.attributes["name"]
|
|
33
|
+
next unless name
|
|
34
|
+
|
|
35
|
+
context[:classes][name] ||= {}
|
|
36
|
+
context[:classes][name].merge!(child.attributes.reject { |key, _| key == "name" })
|
|
37
|
+
|
|
38
|
+
defaults = child.element_children.each_with_object({}) do |class_child, memo|
|
|
39
|
+
memo[class_child.tag_name] = class_child.attributes
|
|
40
|
+
end
|
|
41
|
+
next if defaults.empty?
|
|
42
|
+
|
|
43
|
+
context[:classes_default][name] ||= {}
|
|
44
|
+
context[:classes_default][name].merge!(defaults)
|
|
45
|
+
else
|
|
46
|
+
context[:tag_defaults][child.tag_name] ||= {}
|
|
47
|
+
context[:tag_defaults][child.tag_name].merge!(child.attributes)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -45,6 +45,10 @@ module MjmlRb
|
|
|
45
45
|
renderer.send(:resolved_attributes, node, context)
|
|
46
46
|
end
|
|
47
47
|
|
|
48
|
+
def with_inherited_mj_class(context, node, &block)
|
|
49
|
+
renderer.send(:with_inherited_mj_class, context, node, &block)
|
|
50
|
+
end
|
|
51
|
+
|
|
48
52
|
def raw_inner(node)
|
|
49
53
|
renderer.send(:raw_inner, node)
|
|
50
54
|
end
|
|
@@ -164,14 +164,16 @@ module MjmlRb
|
|
|
164
164
|
end
|
|
165
165
|
|
|
166
166
|
def render_navbar_children(node, context)
|
|
167
|
-
node
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
167
|
+
with_inherited_mj_class(context, node) do
|
|
168
|
+
node.element_children.map do |child|
|
|
169
|
+
case child.tag_name
|
|
170
|
+
when "mj-navbar-link", "mj-raw"
|
|
171
|
+
render_node(child, context, parent: "mj-navbar")
|
|
172
|
+
else
|
|
173
|
+
""
|
|
174
|
+
end
|
|
175
|
+
end.join
|
|
176
|
+
end
|
|
175
177
|
end
|
|
176
178
|
|
|
177
179
|
def render_navbar_link(node, context, attrs, parent:)
|
|
@@ -224,22 +224,24 @@ module MjmlRb
|
|
|
224
224
|
close_tr = %(<!--[if mso | IE]></tr><![endif]-->)
|
|
225
225
|
close_table = %(<!--[if mso | IE]></table><![endif]-->)
|
|
226
226
|
|
|
227
|
-
col_parts =
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
227
|
+
col_parts = with_inherited_mj_class(context, node) do
|
|
228
|
+
columns.each_with_index.map do |col, i|
|
|
229
|
+
col_attrs = resolved_attributes(col, context)
|
|
230
|
+
v_align = col_attrs["vertical-align"] || "top"
|
|
231
|
+
col_px = (box_width.to_f * widths[i] / 100.0).round
|
|
232
|
+
|
|
233
|
+
td_open = %(<!--[if mso | IE]><td class="" style="vertical-align:#{v_align};width:#{col_px}px;" ><![endif]-->)
|
|
234
|
+
td_close = %(<!--[if mso | IE]></td><![endif]-->)
|
|
235
|
+
|
|
236
|
+
col_html = if col.tag_name == "mj-group"
|
|
237
|
+
renderer.send(:render_group, col, context, widths[i])
|
|
238
|
+
else
|
|
239
|
+
context[:_column_width_pct] = widths[i]
|
|
240
|
+
render_node(col, context, parent: "mj-section")
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
"#{td_open}\n#{col_html}\n#{td_close}"
|
|
244
|
+
end
|
|
243
245
|
end
|
|
244
246
|
|
|
245
247
|
([open_table, open_tr] + col_parts + [close_tr, close_table]).join("\n")
|
|
@@ -317,11 +319,13 @@ module MjmlRb
|
|
|
317
319
|
close_tr = %(<!--[if mso | IE]></tr><![endif]-->)
|
|
318
320
|
close_table = %(<!--[if mso | IE]></table><![endif]-->)
|
|
319
321
|
|
|
320
|
-
section_parts =
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
322
|
+
section_parts = with_inherited_mj_class(context, node) do
|
|
323
|
+
children.map do |child|
|
|
324
|
+
td_open = %(<!--[if mso | IE]><td class="" width="#{container_px}px" ><![endif]-->)
|
|
325
|
+
td_close = %(<!--[if mso | IE]></td><![endif]-->)
|
|
326
|
+
child_html = render_node(child, context, parent: "mj-wrapper")
|
|
327
|
+
"#{td_open}\n#{child_html}\n#{td_close}"
|
|
328
|
+
end
|
|
325
329
|
end
|
|
326
330
|
|
|
327
331
|
([open_table, open_tr] + section_parts + [close_tr, close_table]).join("\n")
|
|
@@ -139,14 +139,16 @@ module MjmlRb
|
|
|
139
139
|
outlook_open = %(<!--[if mso | IE]><table align="#{escape_attr(align)}" border="0" cellpadding="0" cellspacing="0" role="presentation" ><tr>)
|
|
140
140
|
outlook_close = %(</tr></table><![endif]-->)
|
|
141
141
|
|
|
142
|
-
children_html =
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
142
|
+
children_html = with_inherited_mj_class(context, node) do
|
|
143
|
+
elements.map.with_index do |child, idx|
|
|
144
|
+
child_attrs = resolved_attributes(child, context)
|
|
145
|
+
merged_attrs = ELEMENT_DEFAULTS.merge(inherited).merge(child_attrs)
|
|
146
|
+
el_html = render_social_element(child, merged_attrs)
|
|
147
|
+
|
|
148
|
+
outlook_td_open = idx == 0 ? "<td>" : "</td><td>"
|
|
149
|
+
%(#{outlook_td_open}<![endif]--><table align="#{escape_attr(align)}" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;"><tbody>#{el_html}</tbody></table><!--[if mso | IE]>)
|
|
150
|
+
end.join
|
|
151
|
+
end
|
|
150
152
|
|
|
151
153
|
%(#{outlook_open}#{children_html}</td>#{outlook_close})
|
|
152
154
|
end
|
|
@@ -155,11 +157,13 @@ module MjmlRb
|
|
|
155
157
|
inherited = inherited_attrs(social_attrs)
|
|
156
158
|
elements = social_element_children(node)
|
|
157
159
|
|
|
158
|
-
children_html =
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
160
|
+
children_html = with_inherited_mj_class(context, node) do
|
|
161
|
+
elements.map do |child|
|
|
162
|
+
child_attrs = resolved_attributes(child, context)
|
|
163
|
+
merged_attrs = ELEMENT_DEFAULTS.merge(inherited).merge(child_attrs)
|
|
164
|
+
render_social_element(child, merged_attrs)
|
|
165
|
+
end.join
|
|
166
|
+
end
|
|
163
167
|
|
|
164
168
|
%(<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="margin:0px;"><tbody>#{children_html}</tbody></table>)
|
|
165
169
|
end
|
data/lib/mjml-rb/parser.rb
CHANGED
|
@@ -20,6 +20,7 @@ module MjmlRb
|
|
|
20
20
|
def parse(mjml, options = {})
|
|
21
21
|
opts = normalize_options(options)
|
|
22
22
|
xml = apply_preprocessors(mjml.to_s, opts[:preprocessors])
|
|
23
|
+
xml = wrap_raw_tags_in_cdata(xml)
|
|
23
24
|
xml = normalize_html_void_tags(xml)
|
|
24
25
|
xml = expand_includes(xml, opts) unless opts[:ignore_includes]
|
|
25
26
|
|
|
@@ -48,6 +49,7 @@ module MjmlRb
|
|
|
48
49
|
end
|
|
49
50
|
|
|
50
51
|
def expand_includes(xml, options)
|
|
52
|
+
xml = wrap_raw_tags_in_cdata(xml)
|
|
51
53
|
xml = normalize_html_void_tags(xml)
|
|
52
54
|
doc = Document.new(sanitize_bare_ampersands(xml))
|
|
53
55
|
includes = XPath.match(doc, "//mj-include")
|
|
@@ -64,7 +66,7 @@ module MjmlRb
|
|
|
64
66
|
replacement = if include_type == "html"
|
|
65
67
|
%(<mj-raw><![CDATA[#{escape_cdata(include_content)}]]></mj-raw>)
|
|
66
68
|
else
|
|
67
|
-
normalize_html_void_tags(strip_xml_declaration(include_content))
|
|
69
|
+
wrap_raw_tags_in_cdata(normalize_html_void_tags(strip_xml_declaration(include_content)))
|
|
68
70
|
end
|
|
69
71
|
|
|
70
72
|
fragment = Document.new(sanitize_bare_ampersands("<include-root>#{replacement}</include-root>"))
|
|
@@ -101,6 +103,18 @@ module MjmlRb
|
|
|
101
103
|
end
|
|
102
104
|
end
|
|
103
105
|
|
|
106
|
+
def wrap_raw_tags_in_cdata(content)
|
|
107
|
+
content.gsub(/<mj-raw(\s[^<>]*?)?>(.*?)<\/mj-raw>/mi) do
|
|
108
|
+
attrs = Regexp.last_match(1).to_s
|
|
109
|
+
inner = Regexp.last_match(2).to_s
|
|
110
|
+
if inner.include?("<![CDATA[")
|
|
111
|
+
"<mj-raw#{attrs}>#{inner}</mj-raw>"
|
|
112
|
+
else
|
|
113
|
+
"<mj-raw#{attrs}><![CDATA[#{escape_cdata(inner)}]]></mj-raw>"
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
104
118
|
def escape_cdata(content)
|
|
105
119
|
content.to_s.gsub("]]>", "]]]]><![CDATA[>")
|
|
106
120
|
end
|
data/lib/mjml-rb/renderer.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
require "cgi"
|
|
2
2
|
require "nokogiri"
|
|
3
3
|
require_relative "components/accordion"
|
|
4
|
+
require_relative "components/attributes"
|
|
4
5
|
require_relative "components/body"
|
|
5
6
|
require_relative "components/breakpoint"
|
|
6
7
|
require_relative "components/button"
|
|
@@ -62,7 +63,9 @@ module MjmlRb
|
|
|
62
63
|
fonts: DEFAULT_FONTS.merge(hash_or_empty(options[:fonts])),
|
|
63
64
|
global_defaults: {},
|
|
64
65
|
tag_defaults: {},
|
|
65
|
-
classes: {}
|
|
66
|
+
classes: {},
|
|
67
|
+
classes_default: {},
|
|
68
|
+
inherited_mj_class: ""
|
|
66
69
|
}
|
|
67
70
|
|
|
68
71
|
return context unless head
|
|
@@ -84,7 +87,11 @@ module MjmlRb
|
|
|
84
87
|
width = node.attributes["width"].to_s.strip
|
|
85
88
|
context[:breakpoint] = width unless width.empty?
|
|
86
89
|
when "mj-attributes"
|
|
87
|
-
|
|
90
|
+
if (component = component_for(node.tag_name)) && component.respond_to?(:handle_head)
|
|
91
|
+
component.handle_head(node, context)
|
|
92
|
+
else
|
|
93
|
+
absorb_attribute_node(node, context)
|
|
94
|
+
end
|
|
88
95
|
when "mj-html-attributes"
|
|
89
96
|
absorb_html_attributes_node(node, context)
|
|
90
97
|
when "mj-raw"
|
|
@@ -173,7 +180,9 @@ module MjmlRb
|
|
|
173
180
|
end
|
|
174
181
|
|
|
175
182
|
def render_children(node, context, parent:)
|
|
176
|
-
|
|
183
|
+
with_inherited_mj_class(context, node) do
|
|
184
|
+
node.children.map { |child| render_node(child, context, parent: parent) }.join("\n")
|
|
185
|
+
end
|
|
177
186
|
end
|
|
178
187
|
|
|
179
188
|
def render_node(node, context, parent:)
|
|
@@ -196,10 +205,12 @@ module MjmlRb
|
|
|
196
205
|
def render_group(node, context, width_pct = 100)
|
|
197
206
|
items = node.element_children.select { |e| e.tag_name == "mj-column" }
|
|
198
207
|
widths = compute_column_widths(items, context)
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
208
|
+
with_inherited_mj_class(context, node) do
|
|
209
|
+
items.each_with_index.map do |item, i|
|
|
210
|
+
context[:_column_width_pct] = widths[i]
|
|
211
|
+
render_node(item, context, parent: "mj-group")
|
|
212
|
+
end.join("\n")
|
|
213
|
+
end
|
|
203
214
|
end
|
|
204
215
|
|
|
205
216
|
def compute_column_widths(columns, context)
|
|
@@ -260,14 +271,10 @@ module MjmlRb
|
|
|
260
271
|
rules.each do |selector, attrs|
|
|
261
272
|
next if selector.empty? || attrs.empty?
|
|
262
273
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
node[name] = value.to_s
|
|
267
|
-
end
|
|
274
|
+
select_nodes(document, selector).each do |node|
|
|
275
|
+
attrs.each do |name, value|
|
|
276
|
+
node[name] = value.to_s
|
|
268
277
|
end
|
|
269
|
-
rescue Nokogiri::CSS::SyntaxError
|
|
270
|
-
next
|
|
271
278
|
end
|
|
272
279
|
end
|
|
273
280
|
|
|
@@ -282,18 +289,53 @@ module MjmlRb
|
|
|
282
289
|
parse_inline_css_rules(css_blocks.join("\n")).each do |selector, declarations|
|
|
283
290
|
next if selector.empty? || declarations.empty?
|
|
284
291
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
merge_inline_style!(node, declarations)
|
|
288
|
-
end
|
|
289
|
-
rescue Nokogiri::CSS::SyntaxError
|
|
290
|
-
next
|
|
292
|
+
select_nodes(document, selector).each do |node|
|
|
293
|
+
merge_inline_style!(node, declarations)
|
|
291
294
|
end
|
|
292
295
|
end
|
|
293
296
|
|
|
294
297
|
document.to_html
|
|
295
298
|
end
|
|
296
299
|
|
|
300
|
+
def select_nodes(document, selector)
|
|
301
|
+
document.css(selector)
|
|
302
|
+
rescue Nokogiri::CSS::SyntaxError, Nokogiri::XML::XPath::SyntaxError
|
|
303
|
+
fallback_select_nodes(document, selector)
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def fallback_select_nodes(document, selector)
|
|
307
|
+
return [] unless selector.include?(":lang(")
|
|
308
|
+
|
|
309
|
+
lang_values = selector.scan(/:lang\(([^)]+)\)/).flatten.map do |value|
|
|
310
|
+
value.to_s.strip.gsub(/\A['"]|['"]\z/, "").downcase
|
|
311
|
+
end.reject(&:empty?)
|
|
312
|
+
return [] if lang_values.empty?
|
|
313
|
+
|
|
314
|
+
base_selector = selector.gsub(/:lang\(([^)]+)\)/, "").strip
|
|
315
|
+
base_selector = "*" if base_selector.empty?
|
|
316
|
+
|
|
317
|
+
document.css(base_selector).select do |node|
|
|
318
|
+
lang_values.all? { |lang| lang_matches?(node, lang) }
|
|
319
|
+
end
|
|
320
|
+
rescue Nokogiri::CSS::SyntaxError, Nokogiri::XML::XPath::SyntaxError
|
|
321
|
+
[]
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
def lang_matches?(node, lang)
|
|
325
|
+
current = node
|
|
326
|
+
|
|
327
|
+
while current
|
|
328
|
+
value = current["lang"]
|
|
329
|
+
if value && !value.empty?
|
|
330
|
+
normalized = value.downcase
|
|
331
|
+
return normalized == lang || normalized.start_with?("#{lang}-")
|
|
332
|
+
end
|
|
333
|
+
current = current.parent
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
false
|
|
337
|
+
end
|
|
338
|
+
|
|
297
339
|
def parse_inline_css_rules(css)
|
|
298
340
|
stripped_css = strip_css_comments(css.to_s)
|
|
299
341
|
plain_css = strip_css_at_rules(stripped_css)
|
|
@@ -391,6 +433,7 @@ module MjmlRb
|
|
|
391
433
|
registry = {}
|
|
392
434
|
# Register component classes here as they are implemented.
|
|
393
435
|
register_component(registry, Components::Body.new(self))
|
|
436
|
+
register_component(registry, Components::Attributes.new(self))
|
|
394
437
|
register_component(registry, Components::Breakpoint.new(self))
|
|
395
438
|
register_component(registry, Components::Accordion.new(self))
|
|
396
439
|
register_component(registry, Components::Button.new(self))
|
|
@@ -419,8 +462,18 @@ module MjmlRb
|
|
|
419
462
|
attrs.merge!(context[:tag_defaults][node.tag_name] || {})
|
|
420
463
|
|
|
421
464
|
node_classes = node.attributes["mj-class"].to_s.split(/\s+/).reject(&:empty?)
|
|
422
|
-
node_classes.
|
|
423
|
-
|
|
465
|
+
class_attrs = node_classes.each_with_object({}) do |klass, memo|
|
|
466
|
+
mj_class_attrs = (context[:classes] || {})[klass] || {}
|
|
467
|
+
if memo["css-class"] && mj_class_attrs["css-class"]
|
|
468
|
+
memo["css-class"] = "#{memo["css-class"]} #{mj_class_attrs["css-class"]}"
|
|
469
|
+
end
|
|
470
|
+
memo.merge!(mj_class_attrs)
|
|
471
|
+
end
|
|
472
|
+
attrs.merge!(class_attrs)
|
|
473
|
+
|
|
474
|
+
inherited_classes = context[:inherited_mj_class].to_s.split(/\s+/).reject(&:empty?)
|
|
475
|
+
inherited_classes.each do |klass|
|
|
476
|
+
attrs.merge!(((context[:classes_default] || {})[klass] || {})[node.tag_name] || {})
|
|
424
477
|
end
|
|
425
478
|
|
|
426
479
|
attrs.merge!(node.attributes)
|
|
@@ -482,6 +535,15 @@ module MjmlRb
|
|
|
482
535
|
node.element_children.find { |child| child.tag_name == tag_name }
|
|
483
536
|
end
|
|
484
537
|
|
|
538
|
+
def with_inherited_mj_class(context, node)
|
|
539
|
+
previous = context[:inherited_mj_class]
|
|
540
|
+
current = node.attributes["mj-class"]
|
|
541
|
+
context[:inherited_mj_class] = (current && !current.empty?) ? current : previous
|
|
542
|
+
yield
|
|
543
|
+
ensure
|
|
544
|
+
context[:inherited_mj_class] = previous
|
|
545
|
+
end
|
|
546
|
+
|
|
485
547
|
def root_file_start_raw(document)
|
|
486
548
|
document.element_children.filter_map do |child|
|
|
487
549
|
next unless child.tag_name == "mj-raw"
|
data/lib/mjml-rb/version.rb
CHANGED
data/mjml-rb.gemspec
CHANGED
|
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
|
|
|
11
11
|
spec.license = "MIT"
|
|
12
12
|
spec.required_ruby_version = ">= 3.0"
|
|
13
13
|
|
|
14
|
-
spec.homepage = "https://github.com/faraquet/mjml-
|
|
14
|
+
spec.homepage = "https://github.com/faraquet/mjml-rb"
|
|
15
15
|
spec.files = Dir.chdir(__dir__) do
|
|
16
16
|
Dir.glob("{bin,lib}/**/*") + ["Gemfile", "LICENSE", "mjml-rb.gemspec", "README.md"]
|
|
17
17
|
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.
|
|
4
|
+
version: 0.2.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrei Andriichuk
|
|
@@ -54,6 +54,7 @@ files:
|
|
|
54
54
|
- lib/mjml-rb/cli.rb
|
|
55
55
|
- lib/mjml-rb/compiler.rb
|
|
56
56
|
- lib/mjml-rb/components/accordion.rb
|
|
57
|
+
- lib/mjml-rb/components/attributes.rb
|
|
57
58
|
- lib/mjml-rb/components/base.rb
|
|
58
59
|
- lib/mjml-rb/components/body.rb
|
|
59
60
|
- lib/mjml-rb/components/breakpoint.rb
|
|
@@ -78,7 +79,7 @@ files:
|
|
|
78
79
|
- lib/mjml-rb/validator.rb
|
|
79
80
|
- lib/mjml-rb/version.rb
|
|
80
81
|
- mjml-rb.gemspec
|
|
81
|
-
homepage: https://github.com/faraquet/mjml-
|
|
82
|
+
homepage: https://github.com/faraquet/mjml-rb
|
|
82
83
|
licenses:
|
|
83
84
|
- MIT
|
|
84
85
|
metadata: {}
|