mjml-rb 0.2.28 → 0.2.29

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: d26c85019dd29ae196f94e69312c51874ee3fe9feccab39d74c0e91442c60746
4
- data.tar.gz: 4bda9f651c55981d6a899a65ac97fafe73c98c85552c1d6c121c93c40ce1ab0d
3
+ metadata.gz: 5cd5e68ad11e8256180f4be4f8d00e86f647327695c9391904eb4ddd31ddc7ad
4
+ data.tar.gz: a7f4f27a8db1ab489a3ae5d2e4e52f556fed859cfc5c8d33beb6ea29a581e6e9
5
5
  SHA512:
6
- metadata.gz: 34a1e8d518b0e7a650ee34505070344fc8c4509e9ccb54bb76eb7032a2a12071d3d5b8b5d8587a86ba4e94f83703f68d841896bbf6260595cc921affe0267fb5
7
- data.tar.gz: 6718b0725c742ef05549cc32301a26a7012afb238da2d796ce1c6b4c54d66cc015fc7e0ae723953b8cd3972a9a7fbcfc914b69c52fdaed50bc8b758c639e5d12
6
+ metadata.gz: 34a43b990be0b2cb7176e8c4736f17a79adc327a67ab91bab09b80af9d5776ad908f18bfba5cf41cdb7ea7ff5a40ea3e2b1cfd088eb3db99e23eab8132023ade
7
+ data.tar.gz: c5dd780d2529994abcc89680862d29dcba174fa6b085e5d0c3fa588ab0048899c010d9ce4ff9565289459d95c86004ffa6bd328920782746ce94241433e4bc9e
data/README.md CHANGED
@@ -34,46 +34,16 @@ bundle exec bin/mjml --validate example.mjml
34
34
  bundle exec bin/mjml --migrate old.mjml -s
35
35
  ```
36
36
 
37
- ## Migration status
37
+ ## Implementation idea
38
38
 
39
- The table below tracks current JS-to-Ruby migration status for MJML components in this repo.
40
-
41
- | Component | Status | Notes |
42
- | --- | --- | --- |
43
- | `mj-body` | migrated | Ruby component exists and matches the upstream `mj-body` behavior currently vendored in `lib/mjml-rb/components/mjml-body`. |
44
- | `mj-section` | migrated | Implemented in `section.rb`. |
45
- | `mj-wrapper` | migrated | Implemented via `section.rb`. |
46
- | `mj-column` | migrated | Implemented in `column.rb`. |
47
- | `mj-group` | migrated | Implemented in `group.rb`, including width-aware child rendering and Outlook table wrappers. |
48
- | `mj-text` | migrated | Implemented in `text.rb`. |
49
- | `mj-image` | migrated | Implemented in `image.rb`. |
50
- | `mj-button` | migrated | Implemented in `button.rb`. |
51
- | `mj-divider` | migrated | Implemented in `divider.rb`. |
52
- | `mj-table` | migrated | Implemented in `table.rb`. |
53
- | `mj-social` | migrated | Implemented in `social.rb`. |
54
- | `mj-social-element` | migrated | Implemented in `social.rb`. |
55
- | `mj-accordion` | migrated | Implemented in `accordion.rb`. |
56
- | `mj-accordion-element` | migrated | Implemented in `accordion.rb`. |
57
- | `mj-accordion-title` | migrated | Implemented in `accordion.rb`. |
58
- | `mj-accordion-text` | migrated | Implemented in `accordion.rb`. |
59
- | `mj-spacer` | migrated | Implemented in `spacer.rb`. |
60
- | `mj-hero` | migrated | Implemented in `hero.rb` with fixed/fluid modes, inner content wrapper, and Outlook VML background fallback. |
61
- | `mj-navbar` | migrated | Implemented in `navbar.rb`, including `base-url` propagation and breakpoint-aware hamburger CSS. |
62
- | `mj-navbar-link` | migrated | Implemented in `navbar.rb` as an ending-tag navbar child component. |
63
- | `mj-raw` | migrated | Implemented in `raw.rb`, including head insertion and top-level `position="file-start"` output before the doctype. |
64
- | `mj-head` | migrated | Implemented in `head.rb` and dispatches supported head children through component handlers. |
65
- | `mj-attributes` | migrated | Implemented in `attributes.rb`, including npm-style `mj-class` descendant defaults. |
66
- | `mj-all` | migrated | Implemented through `attributes.rb` with npm-style global default attribute precedence. |
67
- | `mj-class` | migrated | Supported through `attributes.rb`, including nested per-tag descendant defaults. |
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`. |
72
- | `mj-carousel` | migrated | Implemented in `carousel.rb`, including per-instance radio/thumbnail CSS, Outlook fallback rendering, and thumbnail/control output. |
73
- | `mj-carousel-image` | migrated | Implemented in `carousel_image.rb`, including radio, thumbnail, and main image rendering helpers used by `mj-carousel`. |
74
- | `mj-breakpoint` | migrated | Supported in `mj-head` and used to control desktop column media-query widths. |
75
- | `mj-html-attributes` | migrated | Supported in `mj-head` and applied to the rendered HTML via CSS selectors. |
76
- | `mj-selector` | migrated | Supported as the selector container for `mj-html-attribute` rules. |
77
- | `mj-html-attribute` | migrated | Supported for injecting custom HTML attributes into matched rendered nodes. |
78
-
79
- A more detailed parity backlog lives in [doc/TODO.md](/doc/TODO.md).
39
+ > **Zero-dependency pure-Ruby MJML renderer.**
40
+ >
41
+ > The npm `mjml` package requires Node.js at build time (or runtime via a child
42
+ > process / FFI bridge). This project replaces that entire pipeline with a single
43
+ > Ruby library: XML parsing, AST construction, attribute resolution, validation,
44
+ > and HTML rendering all in Ruby, with no native extensions and no Node.js
45
+ > dependency. Drop it into a Rails, Sinatra, or plain Ruby project and render
46
+ > MJML templates the same way you render ERB — no extra runtime, no
47
+ > `package.json`, no `node_modules`.
48
+
49
+ Remaining parity work is tracked in [doc/TODO.md](/doc/TODO.md).
@@ -50,7 +50,7 @@ module MjmlRb
50
50
  return "" if children.empty?
51
51
 
52
52
  carousel_id = SecureRandom.hex(8)
53
- context[:head_styles] << component_head_style(carousel_id, children.length, a)
53
+ context[:component_head_styles] << component_head_style(carousel_id, children.length, a)
54
54
 
55
55
  outer_td_attrs = {
56
56
  "align" => a["align"],
@@ -49,7 +49,7 @@ module MjmlRb
49
49
  if node.attributes["inline"] == "inline"
50
50
  context[:inline_styles] << css
51
51
  else
52
- context[:head_styles] << css
52
+ context[:user_styles] << css
53
53
  end
54
54
  when "mj-font"
55
55
  name = node.attributes["name"]
@@ -71,7 +71,6 @@ module MjmlRb
71
71
  context[:column_widths] = {}
72
72
  append_component_head_styles(document, context)
73
73
  content = render_node(body, context, parent: "mjml")
74
- append_column_width_styles(context)
75
74
  build_html_document(content, context)
76
75
  end
77
76
 
@@ -84,7 +83,8 @@ module MjmlRb
84
83
  breakpoint: "480px",
85
84
  before_doctype: "",
86
85
  head_raw: [],
87
- head_styles: [],
86
+ component_head_styles: [],
87
+ user_styles: [],
88
88
  inline_styles: [],
89
89
  html_attributes: {},
90
90
  fonts: DEFAULT_FONTS.merge(hash_or_empty(options[:fonts])),
@@ -108,10 +108,12 @@ module MjmlRb
108
108
  def build_html_document(content, context)
109
109
  title = context[:title].to_s
110
110
  preview = context[:preview]
111
- head_styles = ([DOCUMENT_RESET_CSS] + unique_strings(context[:head_styles])).join("\n")
112
111
  head_raw = Array(context[:head_raw]).join("\n")
113
112
  before_doctype = context[:before_doctype].to_s
114
113
  font_tags = build_font_tags(content, context[:inline_styles], context[:fonts])
114
+ media_queries_tags = build_media_queries_tags(context[:breakpoint], context[:column_widths])
115
+ component_styles_tag = build_style_tag(unique_strings(context[:component_head_styles]))
116
+ user_styles_tag = build_style_tag(unique_strings(context[:user_styles]))
115
117
  preview_block = preview.empty? ? "" : %(<div style="display:none;max-height:0;overflow:hidden;opacity:0;">#{escape_html(preview)}</div>)
116
118
  html_attributes = {
117
119
  "lang" => context[:lang],
@@ -133,10 +135,13 @@ module MjmlRb
133
135
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
134
136
  <meta charset="utf-8">
135
137
  <meta name="viewport" content="width=device-width, initial-scale=1">
138
+ <style type="text/css">#{DOCUMENT_RESET_CSS}</style>
136
139
  #{OUTLOOK_DOCUMENT_SETTINGS}
137
140
  #{OUTLOOK_GROUP_FIX}
138
141
  #{font_tags}
139
- <style type="text/css">#{head_styles}</style>
142
+ #{media_queries_tags}
143
+ #{component_styles_tag}
144
+ #{user_styles_tag}
140
145
  #{head_raw}
141
146
  </head>
142
147
  <body style="#{body_style}">
@@ -226,28 +231,39 @@ module MjmlRb
226
231
  end
227
232
  end
228
233
 
229
- def append_column_width_styles(context)
230
- widths = context[:column_widths] || {}
231
- return if widths.empty?
234
+ def build_media_queries_tags(breakpoint, column_widths)
235
+ widths = column_widths || {}
236
+ return "" if widths.empty?
232
237
 
233
- css = widths.map do |suffix, pct|
238
+ base_rules = widths.map do |suffix, pct|
234
239
  ".mj-column-per-#{suffix} { width:#{pct}% !important; max-width: #{pct}%; }"
235
- end.join("\n")
236
- moz_css = widths.map do |suffix, pct|
240
+ end
241
+ moz_rules = widths.map do |suffix, pct|
237
242
  ".moz-text-html .mj-column-per-#{suffix} { width:#{pct}% !important; max-width: #{pct}%; }"
238
- end.join("\n")
239
- owa_css = widths.map do |suffix, pct|
243
+ end
244
+ owa_rules = widths.map do |suffix, pct|
240
245
  "[owa] .mj-column-per-#{suffix} { width:#{pct}% !important; max-width: #{pct}%; }"
241
- end.join("\n")
242
- breakpoint = context[:breakpoint].to_s.strip
243
- if breakpoint.empty?
244
- context[:head_styles] << css
245
- context[:head_styles] << moz_css
246
+ end
247
+
248
+ bp = breakpoint.to_s.strip
249
+ parts = []
250
+
251
+ if bp.empty?
252
+ parts << "<style type=\"text/css\">\n#{base_rules.join("\n")}\n</style>"
253
+ parts << "<style type=\"text/css\">\n#{moz_rules.join("\n")}\n</style>"
246
254
  else
247
- context[:head_styles] << "@media only screen and (min-width:#{breakpoint}) {\n#{css}\n}"
248
- context[:head_styles] << "@media screen and (min-width:#{breakpoint}) {\n#{moz_css}\n}"
255
+ parts << "<style type=\"text/css\">\n@media only screen and (min-width:#{bp}) {\n#{base_rules.join("\n")}\n}\n</style>"
256
+ parts << "<style media=\"screen and (min-width:#{bp})\">\n#{moz_rules.join("\n")}\n</style>"
249
257
  end
250
- context[:head_styles] << owa_css
258
+
259
+ parts << "<style type=\"text/css\">\n#{owa_rules.join("\n")}\n</style>"
260
+ parts.join("\n")
261
+ end
262
+
263
+ def build_style_tag(styles)
264
+ return "" if styles.empty?
265
+
266
+ "<style type=\"text/css\">#{styles.join("\n")}</style>"
251
267
  end
252
268
 
253
269
  def merge_outlook_conditionals(html)
@@ -482,7 +498,7 @@ module MjmlRb
482
498
  end
483
499
  next unless Array(tags).any? { |tag| contains_tag?(document, tag) }
484
500
 
485
- context[:head_styles] << style
501
+ context[:component_head_styles] << style
486
502
  end
487
503
  end
488
504
 
@@ -1,3 +1,3 @@
1
1
  module MjmlRb
2
- VERSION = "0.2.28".freeze
2
+ VERSION = "0.2.29".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.28
4
+ version: 0.2.29
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrei Andriichuk