mjml-rb 0.4.1 → 0.4.3

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: 3b8ae00ad7838acf9020b38b585a90fbf86981d234e813da8f9d45d52421dd1a
4
- data.tar.gz: b6723a5a59e85e6db01ae7bcb8e8b185c6cbd5253fccd195f93aa6158fa08361
3
+ metadata.gz: a143101aa0a18f9aa1e77160d0810561f9b5e58fe6dbf8c68d9f809b91421d8d
4
+ data.tar.gz: b3fb93b17518384e6ce03e96162a30ef870ab4534bfc3c0fe62775b5db21c594
5
5
  SHA512:
6
- metadata.gz: d7ad920fbb9996b3bdb4f0321420121695ebf3124ce466e19059b38f72add99284f8208fd2d985313b0e217762f5676ed669cb438cbaec6b68685366e43186c8
7
- data.tar.gz: 853ffaae0d6889dc630837b73856204bc68d42c8eb7a01dc125c6629a2b9b0258a6fc73a22855e553f1c34a2b2d4876a04e76598e8e0f3be5c5bd6e99ef99fe1
6
+ metadata.gz: a811a85d468e8332b29c533efe918038c68fccb9f7383d82bfceff42954cbe7c9faeccbe72dde57380b422881b620a358db2d6032bef62928053a6b25c001d71
7
+ data.tar.gz: c04f1e1df5835cbf5d0c2f81835ba942aa6f9d3149e592662cffeec22ece7c2841d89f44d4834e5ba29ef8b2891070c402a9da277b04363a7f59541740f2c785
data/README.md CHANGED
@@ -11,198 +11,98 @@
11
11
  > This is a **fully open source project** — feedback, bug reports, test cases,
12
12
  > and pull requests are welcome!
13
13
 
14
- This gem provides a Ruby-first implementation of the main MJML tooling:
14
+ A pure-Ruby MJML v4 compiler no Node.js required.
15
15
 
16
- - library API compatible with `mjml2html`
17
- - command-line interface (`mjml`)
18
- - validation commands
19
- - pure Ruby parser, AST, validator, and renderer
20
- - no Node.js runtime and no shelling out to the official npm renderer
16
+ - Library API compatible with `mjml2html`
17
+ - Command-line interface (`mjml`)
18
+ - Rails integration (ActionView template handler for `.mjml` views)
19
+ - Validation (soft, strict, skip)
20
+ - Custom component support
21
+ - Pure Ruby parser, AST, validator, and renderer
21
22
 
22
- Remaining parity work is tracked in [npm Ruby Parity Audit](docs/PARITY_AUDIT.md).
23
-
24
- ## Compatibility
25
-
26
- This project targets **MJML v4 only**.
23
+ **[Full Usage Guide](docs/USAGE.md)** API reference, all compiler options, component attribute tables, CLI flags, Rails setup, custom components, and more.
27
24
 
28
- - parsing, validation, and rendering are implemented against the MJML v4 document structure
29
- - component rules and attribute validation follow the MJML v4 model
25
+ ## Installation
30
26
 
31
- ## Quick start
32
-
33
- ```bash
34
- bundle install
35
- bundle exec ruby -Ilib -e 'require "mjml-rb"; puts MjmlRb.mjml2html("<mjml><mj-body><mj-section><mj-column><mj-text>Hello</mj-text></mj-column></mj-section></mj-body></mjml>")[:html]'
27
+ ```ruby
28
+ # Gemfile
29
+ gem "mjml-rb"
36
30
  ```
37
31
 
38
- ## CLI usage
32
+ ## Quick Start
39
33
 
40
- ```bash
41
- bundle exec bin/mjml example.mjml -o output.html
42
- bundle exec bin/mjml --validate example.mjml
34
+ ```ruby
35
+ require "mjml-rb"
36
+
37
+ result = MjmlRb.mjml2html(<<~MJML)
38
+ <mjml>
39
+ <mj-body>
40
+ <mj-section>
41
+ <mj-column>
42
+ <mj-text>Hello World!</mj-text>
43
+ </mj-column>
44
+ </mj-section>
45
+ </mj-body>
46
+ </mjml>
47
+ MJML
48
+
49
+ puts result[:html] # compiled HTML
50
+ puts result[:errors] # validation errors (if any)
43
51
  ```
44
52
 
45
- ## Rails integration
46
-
47
- In a Rails app, requiring the gem registers an `ActionView` template handler for
48
- `.mjml` templates through a `Railtie`.
49
-
50
- By default, `.mjml` files are treated as raw MJML/XML source.
53
+ ## CLI
51
54
 
52
- If you want Slim-backed MJML templates, configure it explicitly:
53
-
54
- ```ruby
55
- config.mjml_rb.rails_template_language = :slim
55
+ ```bash
56
+ mjml email.mjml -o email.html # compile to file
57
+ mjml -r "templates/*.mjml" -o output/ # batch compile
58
+ mjml -v email.mjml # validate only
59
+ mjml -i -s < email.mjml # stdin → stdout
56
60
  ```
57
61
 
58
- Supported values are `:slim` and `:haml`.
62
+ See the [Usage Guide — CLI section](docs/USAGE.md#cli) for all flags and config options.
59
63
 
60
- With a configured `rails_template_language`, `.mjml` templates are rendered
61
- through that template engine first, so partials and embedded Ruby can assemble
62
- MJML before the outer template is compiled to HTML. Without that setting,
63
- non-XML MJML source is rejected instead of being guessed.
64
+ ## Rails
64
65
 
65
- For `:slim` or `:haml`, the matching Rails template handler must already be
66
- registered in `ActionView` by the corresponding gem or integration layer.
66
+ Add the gem to your Gemfile that's it. The `.mjml` template handler is registered automatically.
67
67
 
68
- Create a view such as `app/views/user_mailer/welcome.html.mjml`:
69
-
70
- ```mjml
68
+ ```erb
69
+ <!-- app/views/user_mailer/welcome.html.mjml -->
71
70
  <mjml>
72
71
  <mj-body>
73
72
  <mj-section>
74
73
  <mj-column>
75
- <mj-text>Hello from Rails</mj-text>
74
+ <mj-text>Welcome, <%= @user.name %>!</mj-text>
76
75
  </mj-column>
77
76
  </mj-section>
78
77
  </mj-body>
79
78
  </mjml>
80
79
  ```
81
80
 
82
- Then render it like any other Rails template:
83
-
84
81
  ```ruby
85
82
  class UserMailer < ApplicationMailer
86
- def welcome
87
- mail(to: "user@example.com", subject: "Welcome")
83
+ def welcome(user)
84
+ @user = user
85
+ mail(to: user.email, subject: "Welcome")
88
86
  end
89
87
  end
90
88
  ```
91
89
 
92
- Rails rendering uses strict MJML validation by default. You can override the
93
- compiler options in your application config:
94
-
95
- ```ruby
96
- config.mjml_rb.compiler_options = { validation_level: "soft" }
97
- ```
98
-
99
- ## Custom components
90
+ Supports Slim and Haml via `config.mjml_rb.rails_template_language = :slim`. See the [Usage Guide — Rails section](docs/USAGE.md#rails-integration) for full configuration.
100
91
 
101
- You can register custom MJML components written in Ruby:
102
-
103
- ```ruby
104
- class MjRating < MjmlRb::Components::Base
105
- TAGS = ["mj-rating"].freeze
106
- ALLOWED_ATTRIBUTES = { "stars" => "integer", "color" => "color" }.freeze
107
- DEFAULT_ATTRIBUTES = { "stars" => "5", "color" => "#f4b400" }.freeze
108
-
109
- def render(tag_name:, node:, context:, attrs:, parent:)
110
- stars = (attrs["stars"] || "5").to_i
111
- color = attrs["color"] || "#f4b400"
112
- %(<div style="color:#{escape_attr(color)}">#{"\u2605" * stars}</div>)
113
- end
114
- end
115
-
116
- MjmlRb.register_component(MjRating,
117
- dependencies: { "mj-column" => ["mj-rating"] },
118
- ending_tags: ["mj-rating"]
119
- )
120
- ```
121
-
122
- The `dependencies` hash declares which parent tags accept the new component as a child. The `ending_tags` list tells the parser to treat content as raw HTML (like `mj-text`). Both are optional.
123
-
124
- Once registered, the component works in MJML markup and is validated like any built-in component.
125
-
126
- ## `.mjmlrc` config file
127
-
128
- Place a `.mjmlrc` file (JSON) in your project root to auto-register custom components and set default compiler options:
92
+ ## Architecture
129
93
 
130
- ```json
131
- {
132
- "packages": [
133
- "./lib/mjml_components/mj_rating.rb"
134
- ],
135
- "options": {
136
- "beautify": true,
137
- "validation-level": "soft"
138
- }
139
- }
140
94
  ```
141
-
142
- - **`packages`** — Ruby files to `require`. Each file should call `MjmlRb.register_component` to register its components.
143
- - **`options`** — Default compiler options. CLI flags and programmatic options override these.
144
-
145
- The CLI loads `.mjmlrc` automatically from the working directory. For the library API, load it explicitly:
146
-
147
- ```ruby
148
- MjmlRb::ConfigFile.load("/path/to/project")
149
- result = MjmlRb.mjml2html(mjml_string)
95
+ MJML string → Parser → AST → Validator → Renderer → HTML
150
96
  ```
151
97
 
152
- ## Architecture
98
+ 1. **Parser** — normalizes source, expands `mj-include`, builds `AstNode` tree
99
+ 2. **Validator** — checks structure, hierarchy, and attribute types
100
+ 3. **Renderer** — resolves head metadata, applies defaults, emits responsive HTML
153
101
 
154
- The compile pipeline is intentionally simple and fully Ruby-based:
155
-
156
- 1. `MjmlRb.mjml2html` calls `MjmlRb::Compiler`.
157
- 2. `MjmlRb::Parser` normalizes the source, expands `mj-include`, and builds an `AstNode` tree.
158
- 3. `MjmlRb::Validator` checks structural rules and supported attributes.
159
- 4. `MjmlRb::Renderer` resolves head metadata, applies component defaults, and renders HTML.
160
- 5. `MjmlRb::Compiler` post-processes the output and returns a `Result`.
161
-
162
- The key architectural idea is that the project uses a small shared AST plus a component registry:
163
-
164
- - the parser produces generic `AstNode` objects instead of component-specific node types
165
- - structure rules live in `lib/mjml-rb/dependencies.rb`
166
- - rendering logic lives in `lib/mjml-rb/components/*`
167
- - head components populate a shared rendering context
168
- - body components consume that context and emit the final HTML
169
-
170
- That split keeps the compiler pipeline predictable:
171
-
172
- - parsing is responsible for source normalization and include expansion
173
- - validation is responsible for MJML structure and attribute checks
174
- - rendering is responsible for HTML generation and responsive email output
175
-
176
- ## Project structure
177
-
178
- The main files are organized like this:
179
-
180
- ```text
181
- lib/mjml-rb.rb # public gem entry point
182
- lib/mjml-rb/compiler.rb # orchestration: parse -> validate -> render
183
- lib/mjml-rb/parser.rb # MJML/XML normalization, includes, AST building
184
- lib/mjml-rb/ast_node.rb # shared tree representation
185
- lib/mjml-rb/validator.rb # structural and attribute validation
186
- lib/mjml-rb/dependencies.rb # allowed parent/child relationships
187
- lib/mjml-rb/renderer.rb # HTML document assembly and render context
188
- lib/mjml-rb/components/* # per-component rendering and head handling
189
- lib/mjml-rb/result.rb # result object returned by the compiler
190
- lib/mjml-rb/cli.rb # CLI implementation used by bin/mjml
191
- docs/ARCHITECTURE.md # deeper architecture notes
192
- docs/PARITY_AUDIT.md # npm vs Ruby parity tracking
193
- ```
102
+ For the full internal walkthrough, see [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).
194
103
 
195
- If you want the full internal walkthrough, see [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).
104
+ Remaining parity work is tracked in [npm Ruby Parity Audit](docs/PARITY_AUDIT.md).
196
105
 
197
- ## Implementation goal
106
+ ## License
198
107
 
199
- > **Ruby MJML pipeline without the Node.js renderer.**
200
- >
201
- > The npm `mjml` package requires Node.js at build time (or runtime via a child
202
- > process / FFI bridge). This project replaces that entire pipeline with a single
203
- > Ruby library: XML parsing, AST construction, attribute resolution, validation,
204
- > and HTML rendering — all in Ruby, with no Node.js runtime and no need to
205
- > shell out to the official MJML renderer. Drop it into a Rails, Sinatra, or
206
- > plain Ruby project and render MJML templates the same way you render ERB — no
207
- > extra runtime, no
208
- > `package.json`, no `node_modules`.
108
+ MIT
@@ -104,7 +104,7 @@ module MjmlRb
104
104
  .moz-text-html input.mj-accordion-checkbox + * .mj-accordion-ico { display:none; }
105
105
  CSS
106
106
 
107
- DEFAULTS = {
107
+ DEFAULT_ATTRIBUTES = {
108
108
  "border" => "2px solid black",
109
109
  "font-family" => "Ubuntu, Helvetica, Arial, sans-serif",
110
110
  "icon-align" => "middle",
@@ -118,21 +118,17 @@ module MjmlRb
118
118
  "padding" => "10px 25px"
119
119
  }.freeze
120
120
 
121
- TITLE_DEFAULTS = {
121
+ TITLE_DEFAULT_ATTRIBUTES = {
122
122
  "font-size" => "13px",
123
123
  "padding" => "16px"
124
124
  }.freeze
125
125
 
126
- TEXT_DEFAULTS = {
126
+ TEXT_DEFAULT_ATTRIBUTES = {
127
127
  "font-size" => "13px",
128
128
  "line-height" => "1",
129
129
  "padding" => "16px"
130
130
  }.freeze
131
131
 
132
- def tags
133
- TAGS
134
- end
135
-
136
132
  def head_style
137
133
  HEAD_STYLE
138
134
  end
@@ -146,11 +142,11 @@ module MjmlRb
146
142
  when "mj-accordion"
147
143
  render_accordion(node, context, attrs)
148
144
  when "mj-accordion-element"
149
- render_accordion_element(node, context, DEFAULTS.merge(attrs))
145
+ render_accordion_element(node, context, DEFAULT_ATTRIBUTES.merge(attrs))
150
146
  when "mj-accordion-title"
151
- render_accordion_title(node, DEFAULTS.merge(attrs))
147
+ render_accordion_title(node, DEFAULT_ATTRIBUTES.merge(attrs))
152
148
  when "mj-accordion-text"
153
- render_accordion_text(node, DEFAULTS.merge(attrs))
149
+ render_accordion_text(node, DEFAULT_ATTRIBUTES.merge(attrs))
154
150
  else
155
151
  render_children(node, context, parent: parent)
156
152
  end
@@ -159,7 +155,7 @@ module MjmlRb
159
155
  private
160
156
 
161
157
  def render_accordion(node, context, attrs)
162
- accordion_attrs = DEFAULTS.merge(attrs)
158
+ accordion_attrs = DEFAULT_ATTRIBUTES.merge(attrs)
163
159
  outer_style = style_join(
164
160
  "padding" => accordion_attrs["padding"],
165
161
  "background-color" => accordion_attrs["container-background-color"]
@@ -225,7 +221,7 @@ module MjmlRb
225
221
  end
226
222
 
227
223
  def render_accordion_title(node, attrs)
228
- title_attrs = TITLE_DEFAULTS.merge(attrs)
224
+ title_attrs = TITLE_DEFAULT_ATTRIBUTES.merge(attrs)
229
225
  td_style = style_join(
230
226
  "width" => "100%",
231
227
  "background-color" => title_attrs["background-color"],
@@ -262,7 +258,7 @@ module MjmlRb
262
258
  end
263
259
 
264
260
  def render_accordion_text(node, attrs)
265
- text_attrs = TEXT_DEFAULTS.merge(attrs)
261
+ text_attrs = TEXT_DEFAULT_ATTRIBUTES.merge(attrs)
266
262
  td_style = style_join(
267
263
  "background" => text_attrs["background-color"],
268
264
  "font-size" => text_attrs["font-size"],
@@ -15,10 +15,6 @@ module MjmlRb
15
15
  end
16
16
  end
17
17
 
18
- def tags
19
- TAGS
20
- end
21
-
22
18
  def render(tag_name:, node:, context:, attrs:, parent:)
23
19
  ""
24
20
  end
@@ -14,13 +14,7 @@ module MjmlRb
14
14
  end
15
15
 
16
16
  def default_attributes
17
- if const_defined?(:DEFAULT_ATTRIBUTES)
18
- const_get(:DEFAULT_ATTRIBUTES)
19
- elsif const_defined?(:DEFAULTS)
20
- const_get(:DEFAULTS)
21
- else
22
- {}
23
- end
17
+ const_defined?(:DEFAULT_ATTRIBUTES) ? const_get(:DEFAULT_ATTRIBUTES) : {}
24
18
  end
25
19
  end
26
20
 
@@ -14,10 +14,6 @@ module MjmlRb
14
14
  "width" => "600px"
15
15
  }.freeze
16
16
 
17
- def tags
18
- TAGS
19
- end
20
-
21
17
  def render(tag_name:, node:, context:, attrs:, parent:)
22
18
  return render_children(node, context, parent: parent) unless tag_name == "mj-body"
23
19
 
@@ -13,10 +13,6 @@ module MjmlRb
13
13
  "width" => "480px"
14
14
  }.freeze
15
15
 
16
- def tags
17
- TAGS
18
- end
19
-
20
16
  def render(tag_name:, node:, context:, attrs:, parent:)
21
17
  ""
22
18
  end
@@ -41,7 +41,7 @@ module MjmlRb
41
41
  "width" => "unit(px,%)"
42
42
  }.freeze
43
43
 
44
- DEFAULTS = {
44
+ DEFAULT_ATTRIBUTES = {
45
45
  "align" => "center",
46
46
  "background-color" => "#414141",
47
47
  "border" => "none",
@@ -59,12 +59,8 @@ module MjmlRb
59
59
  "vertical-align" => "middle"
60
60
  }.freeze
61
61
 
62
- def tags
63
- TAGS
64
- end
65
-
66
62
  def render(tag_name:, node:, context:, attrs:, parent:)
67
- a = DEFAULTS.merge(attrs)
63
+ a = DEFAULT_ATTRIBUTES.merge(attrs)
68
64
 
69
65
  bg_color = a["background-color"]
70
66
  inner_padding = a["inner-padding"]
@@ -40,10 +40,6 @@ module MjmlRb
40
40
  "tb-selected-border-color" => "#ccc"
41
41
  }.freeze
42
42
 
43
- def tags
44
- TAGS
45
- end
46
-
47
43
  def render(tag_name:, node:, context:, attrs:, parent:)
48
44
  a = DEFAULT_ATTRIBUTES.merge(attrs)
49
45
  children = carousel_images(node)
@@ -23,10 +23,6 @@ module MjmlRb
23
23
  "target" => "_blank"
24
24
  }.freeze
25
25
 
26
- def tags
27
- TAGS
28
- end
29
-
30
26
  def render(tag_name:, node:, context:, attrs:, parent:)
31
27
  render_item(
32
28
  node,
@@ -19,7 +19,7 @@ module MjmlRb
19
19
  "align" => "enum(left,center,right)"
20
20
  }.freeze
21
21
 
22
- DEFAULTS = {
22
+ DEFAULT_ATTRIBUTES = {
23
23
  "align" => "center",
24
24
  "border-color" => "#000000",
25
25
  "border-style" => "solid",
@@ -28,12 +28,8 @@ module MjmlRb
28
28
  "width" => "100%"
29
29
  }.freeze
30
30
 
31
- def tags
32
- TAGS
33
- end
34
-
35
31
  def render(tag_name:, node:, context:, attrs:, parent:)
36
- a = DEFAULTS.merge(attrs)
32
+ a = DEFAULT_ATTRIBUTES.merge(attrs)
37
33
 
38
34
  outer_td_style = style_join(
39
35
  "background" => a["container-background-color"],
@@ -16,10 +16,6 @@ module MjmlRb
16
16
  "direction" => "ltr"
17
17
  }.freeze
18
18
 
19
- def tags
20
- TAGS
21
- end
22
-
23
19
  def render(tag_name:, node:, context:, attrs:, parent:)
24
20
  width_pct = context.delete(:_column_width_pct) || 100.0
25
21
  a = DEFAULT_ATTRIBUTES.merge(attrs)
@@ -25,10 +25,6 @@ module MjmlRb
25
25
  end
26
26
  end
27
27
 
28
- def tags
29
- TAGS
30
- end
31
-
32
28
  def render(tag_name:, node:, context:, attrs:, parent:)
33
29
  ""
34
30
  end
@@ -43,10 +43,6 @@ module MjmlRb
43
43
  "vertical-align" => "top"
44
44
  }.freeze
45
45
 
46
- def tags
47
- TAGS
48
- end
49
-
50
46
  def render(tag_name:, node:, context:, attrs:, parent:)
51
47
  a = DEFAULT_ATTRIBUTES.merge(attrs)
52
48
  container_width = normalize_container_width(context[:container_width] || "600px")
@@ -37,7 +37,7 @@ module MjmlRb
37
37
  "usemap" => "string"
38
38
  }.freeze
39
39
 
40
- DEFAULTS = {
40
+ DEFAULT_ATTRIBUTES = {
41
41
  "alt" => "",
42
42
  "align" => "center",
43
43
  "border" => "0",
@@ -47,10 +47,6 @@ module MjmlRb
47
47
  "font-size" => "13px"
48
48
  }.freeze
49
49
 
50
- def tags
51
- TAGS
52
- end
53
-
54
50
  def head_style(breakpoint)
55
51
  lower_breakpoint = make_lower_breakpoint(breakpoint)
56
52
 
@@ -67,7 +63,7 @@ module MjmlRb
67
63
  end
68
64
 
69
65
  def render(tag_name:, node:, context:, attrs:, parent:)
70
- a = DEFAULTS.merge(attrs)
66
+ a = DEFAULT_ATTRIBUTES.merge(attrs)
71
67
 
72
68
  fluid = a["fluid-on-mobile"] == "true"
73
69
  full_width = a["full-width"] == "full-width"
@@ -91,10 +91,6 @@ module MjmlRb
91
91
  end
92
92
  end
93
93
 
94
- def tags
95
- TAGS
96
- end
97
-
98
94
  def head_style(breakpoint)
99
95
  lower_breakpoint = make_lower_breakpoint(breakpoint)
100
96
 
@@ -9,10 +9,6 @@ module MjmlRb
9
9
  "position" => "enum(file-start)"
10
10
  }.freeze
11
11
 
12
- def tags
13
- TAGS
14
- end
15
-
16
12
  def render(tag_name:, node:, context:, attrs:, parent:)
17
13
  raw_inner(node)
18
14
  end
@@ -54,10 +54,6 @@ module MjmlRb
54
54
  end
55
55
  end
56
56
 
57
- def tags
58
- TAGS
59
- end
60
-
61
57
  def render(tag_name:, node:, context:, attrs:, parent:)
62
58
  case tag_name
63
59
  when "mj-wrapper"
@@ -607,7 +603,7 @@ module MjmlRb
607
603
  "overflow" => (has_border_radius ? "hidden" : nil),
608
604
  "margin" => "0px auto",
609
605
  "margin-top" => wrapper_gap,
610
- "max-width" => (full_width ? nil : "#{container_px}px")
606
+ "max-width" => "#{container_px}px"
611
607
  }.merge(full_width ? {} : background_styles)
612
608
  )
613
609
 
@@ -107,7 +107,7 @@ module MjmlRb
107
107
  icon-size icon-height icon-padding text-padding line-height text-decoration
108
108
  ].freeze
109
109
 
110
- SOCIAL_DEFAULTS = {
110
+ SOCIAL_DEFAULT_ATTRIBUTES = {
111
111
  "align" => "center",
112
112
  "border-radius" => "3px",
113
113
  "color" => "#333333",
@@ -120,7 +120,7 @@ module MjmlRb
120
120
  "text-decoration" => "none"
121
121
  }.freeze
122
122
 
123
- ELEMENT_DEFAULTS = {
123
+ ELEMENT_DEFAULT_ATTRIBUTES = {
124
124
  "alt" => "",
125
125
  "align" => "left",
126
126
  "icon-position" => "left",
@@ -136,17 +136,13 @@ module MjmlRb
136
136
  "vertical-align" => "middle"
137
137
  }.freeze
138
138
 
139
- def tags
140
- TAGS
141
- end
142
-
143
139
  def render(tag_name:, node:, context:, attrs:, parent:)
144
140
  case tag_name
145
141
  when "mj-social"
146
142
  render_social(node, context, attrs)
147
143
  when "mj-social-element"
148
144
  # Direct dispatch (no parent attrs merging) — fallback for standalone use
149
- render_social_element(node, ELEMENT_DEFAULTS.merge(attrs))
145
+ render_social_element(node, ELEMENT_DEFAULT_ATTRIBUTES.merge(attrs))
150
146
  end
151
147
  end
152
148
 
@@ -155,7 +151,7 @@ module MjmlRb
155
151
  # ── mj-social ──────────────────────────────────────────────────────────
156
152
 
157
153
  def render_social(node, context, attrs)
158
- a = SOCIAL_DEFAULTS.merge(attrs)
154
+ a = SOCIAL_DEFAULT_ATTRIBUTES.merge(attrs)
159
155
 
160
156
  outer_td_style = style_join(
161
157
  "background" => a["container-background-color"],
@@ -211,7 +207,7 @@ module MjmlRb
211
207
  children_html = with_inherited_mj_class(context, node) do
212
208
  elements.map.with_index do |child, idx|
213
209
  child_attrs = resolved_attributes(child, context)
214
- merged_attrs = ELEMENT_DEFAULTS.merge(inherited).merge(child_attrs)
210
+ merged_attrs = ELEMENT_DEFAULT_ATTRIBUTES.merge(inherited).merge(child_attrs)
215
211
  el_html = render_social_element(child, merged_attrs)
216
212
 
217
213
  outlook_td_open = idx == 0 ? "<td>" : "</td><td>"
@@ -229,7 +225,7 @@ module MjmlRb
229
225
  children_html = with_inherited_mj_class(context, node) do
230
226
  elements.map do |child|
231
227
  child_attrs = resolved_attributes(child, context)
232
- merged_attrs = ELEMENT_DEFAULTS.merge(inherited).merge(child_attrs)
228
+ merged_attrs = ELEMENT_DEFAULT_ATTRIBUTES.merge(inherited).merge(child_attrs)
233
229
  render_social_element(child, merged_attrs)
234
230
  end.join
235
231
  end
@@ -24,10 +24,6 @@ module MjmlRb
24
24
  "height" => "20px"
25
25
  }.freeze
26
26
 
27
- def tags
28
- TAGS
29
- end
30
-
31
27
  def render(tag_name:, node:, context:, attrs:, parent:)
32
28
  a = self.class.default_attributes.merge(attrs)
33
29
  height = a["height"]
@@ -27,7 +27,7 @@ module MjmlRb
27
27
  "width" => "unit(px,%,auto)"
28
28
  }.freeze
29
29
 
30
- DEFAULTS = {
30
+ DEFAULT_ATTRIBUTES = {
31
31
  "align" => "left",
32
32
  "border" => "none",
33
33
  "cellpadding" => "0",
@@ -41,12 +41,8 @@ module MjmlRb
41
41
  "width" => "100%"
42
42
  }.freeze
43
43
 
44
- def tags
45
- TAGS
46
- end
47
-
48
44
  def render(tag_name:, node:, context:, attrs:, parent:)
49
- a = DEFAULTS.merge(attrs)
45
+ a = DEFAULT_ATTRIBUTES.merge(attrs)
50
46
 
51
47
  outer_td_style = style_join(
52
48
  "background" => a["container-background-color"],
@@ -27,7 +27,7 @@ module MjmlRb
27
27
  "vertical-align" => "enum(top,bottom,middle)"
28
28
  }.freeze
29
29
 
30
- DEFAULTS = {
30
+ DEFAULT_ATTRIBUTES = {
31
31
  "align" => "left",
32
32
  "color" => "#000000",
33
33
  "font-family" => "Ubuntu, Helvetica, Arial, sans-serif",
@@ -36,12 +36,8 @@ module MjmlRb
36
36
  "padding" => "10px 25px"
37
37
  }.freeze
38
38
 
39
- def tags
40
- TAGS
41
- end
42
-
43
39
  def render(tag_name:, node:, context:, attrs:, parent:)
44
- a = DEFAULTS.merge(attrs)
40
+ a = DEFAULT_ATTRIBUTES.merge(attrs)
45
41
  height = a["height"]
46
42
 
47
43
  outer_td_style = style_join(
@@ -545,6 +545,12 @@ module MjmlRb
545
545
  end
546
546
 
547
547
  def normalize_background_fallbacks!(node, declarations)
548
+ background_image = declaration_value(declarations["background-image"])
549
+ if background_image && !background_image.empty?
550
+ declarations.delete("background") if syncable_background?(declaration_value(declarations["background"]))
551
+ return
552
+ end
553
+
548
554
  background_color = declaration_value(declarations["background-color"])
549
555
  return if background_color.nil? || background_color.empty?
550
556
 
@@ -1,3 +1,3 @@
1
1
  module MjmlRb
2
- VERSION = "0.4.1".freeze
2
+ VERSION = "0.4.3".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.4.1
4
+ version: 0.4.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrei Andriichuk