mjml-rb 0.2.3 → 0.2.5
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 +7 -5
- data/lib/mjml-rb/components/breakpoint.rb +5 -0
- data/lib/mjml-rb/components/head.rb +59 -0
- data/lib/mjml-rb/components/html_attributes.rb +23 -1
- data/lib/mjml-rb/components/raw.rb +4 -0
- data/lib/mjml-rb/renderer.rb +49 -78
- 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: 288347d54c01e4a6c60ed76c71ed988cb74f8a88eed9de7d7fb06cf740bd79e5
|
|
4
|
+
data.tar.gz: 369dc21e8dd16555318abfd6bc4cfa28649e4bbcce10a8a9b9b6f0613c87fb90
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3bf2de79918958b9d4cd9b66191e6b2b52c5494d97d961a129d2d9db7193690a8616063c1168245ced32c736a24caca07aeda5945747d44a972568894b79fd01
|
|
7
|
+
data.tar.gz: e4e56ca10989e917fb5b4d14b4d090099b1003c97599d0dd57330a35c09981dee2424568e00a1b144050d5c564da86e5bf713cbf098065585a985b78a48a2039
|
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:
|
|
@@ -59,14 +61,14 @@ The table below tracks current JS-to-Ruby migration status for MJML components i
|
|
|
59
61
|
| `mj-navbar` | migrated | Implemented in `navbar.rb`, including `base-url` propagation and breakpoint-aware hamburger CSS. |
|
|
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
|
-
| `mj-head` |
|
|
64
|
+
| `mj-head` | migrated | Implemented in `head.rb` and dispatches supported head children through component handlers. |
|
|
63
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
67
|
| `mj-class` | migrated | Supported through `attributes.rb`, including nested per-tag descendant defaults. |
|
|
66
|
-
| `mj-title` |
|
|
67
|
-
| `mj-preview` |
|
|
68
|
-
| `mj-style` |
|
|
69
|
-
| `mj-font` |
|
|
68
|
+
| `mj-title` | migrated | Implemented in `head.rb`. |
|
|
69
|
+
| `mj-preview` | migrated | Implemented in `head.rb`. |
|
|
70
|
+
| `mj-style` | migrated | Implemented in `head.rb`, including inline-style registration. |
|
|
71
|
+
| `mj-font` | migrated | Implemented in `head.rb`. |
|
|
70
72
|
| `mj-carousel` | not migrated | Declared in dependency rules but no renderer implementation yet. |
|
|
71
73
|
| `mj-carousel-image` | not migrated | Declared in dependency rules but no renderer implementation yet. |
|
|
72
74
|
| `mj-breakpoint` | migrated | Supported in `mj-head` and used to control desktop column media-query widths. |
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
require_relative "base"
|
|
2
|
+
|
|
3
|
+
module MjmlRb
|
|
4
|
+
module Components
|
|
5
|
+
class Head < Base
|
|
6
|
+
TAGS = %w[mj-head mj-title mj-preview mj-style mj-font].freeze
|
|
7
|
+
|
|
8
|
+
ALLOWED_ATTRIBUTES = {
|
|
9
|
+
"mj-style" => {
|
|
10
|
+
"inline" => "string"
|
|
11
|
+
},
|
|
12
|
+
"mj-font" => {
|
|
13
|
+
"name" => "string",
|
|
14
|
+
"href" => "string"
|
|
15
|
+
}
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
18
|
+
class << self
|
|
19
|
+
def allowed_attributes_for(tag_name)
|
|
20
|
+
ALLOWED_ATTRIBUTES[tag_name] || {}
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def allowed_attributes
|
|
24
|
+
{}
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def tags
|
|
29
|
+
TAGS
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def render(tag_name:, node:, context:, attrs:, parent:)
|
|
33
|
+
""
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def handle_head(node, context)
|
|
37
|
+
case node.tag_name
|
|
38
|
+
when "mj-head"
|
|
39
|
+
node.element_children.each do |child|
|
|
40
|
+
component = renderer.send(:component_for, child.tag_name)
|
|
41
|
+
component.handle_head(child, context) if component&.respond_to?(:handle_head)
|
|
42
|
+
end
|
|
43
|
+
when "mj-title"
|
|
44
|
+
context[:title] = raw_inner(node).strip
|
|
45
|
+
when "mj-preview"
|
|
46
|
+
context[:preview] = raw_inner(node).strip
|
|
47
|
+
when "mj-style"
|
|
48
|
+
css = raw_inner(node)
|
|
49
|
+
context[:head_styles] << css
|
|
50
|
+
context[:inline_styles] << css if node.attributes["inline"] == "inline"
|
|
51
|
+
when "mj-font"
|
|
52
|
+
name = node.attributes["name"]
|
|
53
|
+
href = node.attributes["href"]
|
|
54
|
+
context[:fonts][name] = href if name && href
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -3,7 +3,7 @@ require_relative "base"
|
|
|
3
3
|
module MjmlRb
|
|
4
4
|
module Components
|
|
5
5
|
class HtmlAttributes < Base
|
|
6
|
-
TAGS = %w[mj-selector mj-html-attribute].freeze
|
|
6
|
+
TAGS = %w[mj-html-attributes mj-selector mj-html-attribute].freeze
|
|
7
7
|
|
|
8
8
|
ALLOWED_ATTRIBUTES = {
|
|
9
9
|
"mj-selector" => {
|
|
@@ -27,6 +27,28 @@ module MjmlRb
|
|
|
27
27
|
def render(tag_name:, node:, context:, attrs:, parent:)
|
|
28
28
|
render_children(node, context, parent: parent)
|
|
29
29
|
end
|
|
30
|
+
|
|
31
|
+
def handle_head(node, context)
|
|
32
|
+
node.element_children.each do |selector|
|
|
33
|
+
next unless selector.tag_name == "mj-selector"
|
|
34
|
+
|
|
35
|
+
path = selector.attributes["path"].to_s.strip
|
|
36
|
+
next if path.empty?
|
|
37
|
+
|
|
38
|
+
custom_attrs = selector.element_children.each_with_object({}) do |child, memo|
|
|
39
|
+
next unless child.tag_name == "mj-html-attribute"
|
|
40
|
+
|
|
41
|
+
name = child.attributes["name"].to_s.strip
|
|
42
|
+
next if name.empty?
|
|
43
|
+
|
|
44
|
+
memo[name] = child.text_content
|
|
45
|
+
end
|
|
46
|
+
next if custom_attrs.empty?
|
|
47
|
+
|
|
48
|
+
context[:html_attributes][path] ||= {}
|
|
49
|
+
context[:html_attributes][path].merge!(custom_attrs)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
30
52
|
end
|
|
31
53
|
end
|
|
32
54
|
end
|
data/lib/mjml-rb/renderer.rb
CHANGED
|
@@ -5,6 +5,7 @@ require_relative "components/attributes"
|
|
|
5
5
|
require_relative "components/body"
|
|
6
6
|
require_relative "components/breakpoint"
|
|
7
7
|
require_relative "components/button"
|
|
8
|
+
require_relative "components/head"
|
|
8
9
|
require_relative "components/hero"
|
|
9
10
|
require_relative "components/image"
|
|
10
11
|
require_relative "components/navbar"
|
|
@@ -71,76 +72,13 @@ module MjmlRb
|
|
|
71
72
|
return context unless head
|
|
72
73
|
|
|
73
74
|
head.element_children.each do |node|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
context[:title] = node.text_content.strip
|
|
77
|
-
when "mj-preview"
|
|
78
|
-
context[:preview] = node.text_content.strip
|
|
79
|
-
when "mj-style"
|
|
80
|
-
context[:head_styles] << node.text_content
|
|
81
|
-
context[:inline_styles] << node.text_content if node.attributes["inline"] == "inline"
|
|
82
|
-
when "mj-font"
|
|
83
|
-
name = node.attributes["name"]
|
|
84
|
-
href = node.attributes["href"]
|
|
85
|
-
context[:fonts][name] = href if name && href
|
|
86
|
-
when "mj-breakpoint"
|
|
87
|
-
width = node.attributes["width"].to_s.strip
|
|
88
|
-
context[:breakpoint] = width unless width.empty?
|
|
89
|
-
when "mj-attributes"
|
|
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
|
|
95
|
-
when "mj-html-attributes"
|
|
96
|
-
absorb_html_attributes_node(node, context)
|
|
97
|
-
when "mj-raw"
|
|
98
|
-
context[:head_raw] << raw_inner(node)
|
|
99
|
-
end
|
|
75
|
+
component = component_for(node.tag_name)
|
|
76
|
+
component.handle_head(node, context) if component&.respond_to?(:handle_head)
|
|
100
77
|
end
|
|
101
78
|
|
|
102
79
|
context
|
|
103
80
|
end
|
|
104
81
|
|
|
105
|
-
def absorb_attribute_node(attributes_node, context)
|
|
106
|
-
attributes_node.element_children.each do |child|
|
|
107
|
-
case child.tag_name
|
|
108
|
-
when "mj-all"
|
|
109
|
-
context[:global_defaults].merge!(child.attributes)
|
|
110
|
-
when "mj-class"
|
|
111
|
-
name = child.attributes["name"]
|
|
112
|
-
next unless name
|
|
113
|
-
|
|
114
|
-
context[:classes][name] = child.attributes.reject { |k, _| k == "name" }
|
|
115
|
-
else
|
|
116
|
-
context[:tag_defaults][child.tag_name] ||= {}
|
|
117
|
-
context[:tag_defaults][child.tag_name].merge!(child.attributes)
|
|
118
|
-
end
|
|
119
|
-
end
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
def absorb_html_attributes_node(html_attributes_node, context)
|
|
123
|
-
html_attributes_node.element_children.each do |selector|
|
|
124
|
-
next unless selector.tag_name == "mj-selector"
|
|
125
|
-
|
|
126
|
-
path = selector.attributes["path"].to_s.strip
|
|
127
|
-
next if path.empty?
|
|
128
|
-
|
|
129
|
-
custom_attrs = selector.element_children.each_with_object({}) do |child, memo|
|
|
130
|
-
next unless child.tag_name == "mj-html-attribute"
|
|
131
|
-
|
|
132
|
-
name = child.attributes["name"].to_s.strip
|
|
133
|
-
next if name.empty?
|
|
134
|
-
|
|
135
|
-
memo[name] = child.text_content
|
|
136
|
-
end
|
|
137
|
-
next if custom_attrs.empty?
|
|
138
|
-
|
|
139
|
-
context[:html_attributes][path] ||= {}
|
|
140
|
-
context[:html_attributes][path].merge!(custom_attrs)
|
|
141
|
-
end
|
|
142
|
-
end
|
|
143
|
-
|
|
144
82
|
def build_html_document(content, context)
|
|
145
83
|
title = context[:title].to_s
|
|
146
84
|
preview = context[:preview]
|
|
@@ -271,14 +209,10 @@ module MjmlRb
|
|
|
271
209
|
rules.each do |selector, attrs|
|
|
272
210
|
next if selector.empty? || attrs.empty?
|
|
273
211
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
node[name] = value.to_s
|
|
278
|
-
end
|
|
212
|
+
select_nodes(document, selector).each do |node|
|
|
213
|
+
attrs.each do |name, value|
|
|
214
|
+
node[name] = value.to_s
|
|
279
215
|
end
|
|
280
|
-
rescue Nokogiri::CSS::SyntaxError
|
|
281
|
-
next
|
|
282
216
|
end
|
|
283
217
|
end
|
|
284
218
|
|
|
@@ -293,18 +227,53 @@ module MjmlRb
|
|
|
293
227
|
parse_inline_css_rules(css_blocks.join("\n")).each do |selector, declarations|
|
|
294
228
|
next if selector.empty? || declarations.empty?
|
|
295
229
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
merge_inline_style!(node, declarations)
|
|
299
|
-
end
|
|
300
|
-
rescue Nokogiri::CSS::SyntaxError
|
|
301
|
-
next
|
|
230
|
+
select_nodes(document, selector).each do |node|
|
|
231
|
+
merge_inline_style!(node, declarations)
|
|
302
232
|
end
|
|
303
233
|
end
|
|
304
234
|
|
|
305
235
|
document.to_html
|
|
306
236
|
end
|
|
307
237
|
|
|
238
|
+
def select_nodes(document, selector)
|
|
239
|
+
document.css(selector)
|
|
240
|
+
rescue Nokogiri::CSS::SyntaxError, Nokogiri::XML::XPath::SyntaxError
|
|
241
|
+
fallback_select_nodes(document, selector)
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def fallback_select_nodes(document, selector)
|
|
245
|
+
return [] unless selector.include?(":lang(")
|
|
246
|
+
|
|
247
|
+
lang_values = selector.scan(/:lang\(([^)]+)\)/).flatten.map do |value|
|
|
248
|
+
value.to_s.strip.gsub(/\A['"]|['"]\z/, "").downcase
|
|
249
|
+
end.reject(&:empty?)
|
|
250
|
+
return [] if lang_values.empty?
|
|
251
|
+
|
|
252
|
+
base_selector = selector.gsub(/:lang\(([^)]+)\)/, "").strip
|
|
253
|
+
base_selector = "*" if base_selector.empty?
|
|
254
|
+
|
|
255
|
+
document.css(base_selector).select do |node|
|
|
256
|
+
lang_values.all? { |lang| lang_matches?(node, lang) }
|
|
257
|
+
end
|
|
258
|
+
rescue Nokogiri::CSS::SyntaxError, Nokogiri::XML::XPath::SyntaxError
|
|
259
|
+
[]
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def lang_matches?(node, lang)
|
|
263
|
+
current = node
|
|
264
|
+
|
|
265
|
+
while current
|
|
266
|
+
value = current["lang"]
|
|
267
|
+
if value && !value.empty?
|
|
268
|
+
normalized = value.downcase
|
|
269
|
+
return normalized == lang || normalized.start_with?("#{lang}-")
|
|
270
|
+
end
|
|
271
|
+
current = current.parent
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
false
|
|
275
|
+
end
|
|
276
|
+
|
|
308
277
|
def parse_inline_css_rules(css)
|
|
309
278
|
stripped_css = strip_css_comments(css.to_s)
|
|
310
279
|
plain_css = strip_css_at_rules(stripped_css)
|
|
@@ -402,6 +371,7 @@ module MjmlRb
|
|
|
402
371
|
registry = {}
|
|
403
372
|
# Register component classes here as they are implemented.
|
|
404
373
|
register_component(registry, Components::Body.new(self))
|
|
374
|
+
register_component(registry, Components::Head.new(self))
|
|
405
375
|
register_component(registry, Components::Attributes.new(self))
|
|
406
376
|
register_component(registry, Components::Breakpoint.new(self))
|
|
407
377
|
register_component(registry, Components::Accordion.new(self))
|
|
@@ -412,6 +382,7 @@ module MjmlRb
|
|
|
412
382
|
register_component(registry, Components::Raw.new(self))
|
|
413
383
|
register_component(registry, Components::Text.new(self))
|
|
414
384
|
register_component(registry, Components::Divider.new(self))
|
|
385
|
+
register_component(registry, Components::HtmlAttributes.new(self))
|
|
415
386
|
register_component(registry, Components::Table.new(self))
|
|
416
387
|
register_component(registry, Components::Social.new(self))
|
|
417
388
|
register_component(registry, Components::Section.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.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrei Andriichuk
|
|
@@ -61,6 +61,7 @@ files:
|
|
|
61
61
|
- lib/mjml-rb/components/button.rb
|
|
62
62
|
- lib/mjml-rb/components/column.rb
|
|
63
63
|
- lib/mjml-rb/components/divider.rb
|
|
64
|
+
- lib/mjml-rb/components/head.rb
|
|
64
65
|
- lib/mjml-rb/components/hero.rb
|
|
65
66
|
- lib/mjml-rb/components/html_attributes.rb
|
|
66
67
|
- lib/mjml-rb/components/image.rb
|