emjay 0.2.0 → 0.2.2
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/lib/emjay/components/body/mj_accordion.rb +0 -3
- data/lib/emjay/components/body/mj_accordion_element.rb +0 -3
- data/lib/emjay/components/body/mj_accordion_text.rb +0 -3
- data/lib/emjay/components/body/mj_accordion_title.rb +0 -3
- data/lib/emjay/components/body/mj_body.rb +0 -3
- data/lib/emjay/components/body/mj_button.rb +0 -3
- data/lib/emjay/components/body/mj_carousel.rb +0 -3
- data/lib/emjay/components/body/mj_carousel_image.rb +0 -3
- data/lib/emjay/components/body/mj_column.rb +1 -4
- data/lib/emjay/components/body/mj_divider.rb +0 -3
- data/lib/emjay/components/body/mj_group.rb +0 -3
- data/lib/emjay/components/body/mj_hero.rb +0 -3
- data/lib/emjay/components/body/mj_image.rb +0 -3
- data/lib/emjay/components/body/mj_navbar.rb +0 -3
- data/lib/emjay/components/body/mj_navbar_link.rb +0 -3
- data/lib/emjay/components/body/mj_raw.rb +0 -3
- data/lib/emjay/components/body/mj_section.rb +0 -3
- data/lib/emjay/components/body/mj_social.rb +0 -3
- data/lib/emjay/components/body/mj_social_element.rb +0 -3
- data/lib/emjay/components/body/mj_spacer.rb +0 -3
- data/lib/emjay/components/body/mj_table.rb +0 -3
- data/lib/emjay/components/body/mj_text.rb +0 -3
- data/lib/emjay/components/body/mj_wrapper.rb +0 -2
- data/lib/emjay/components/head/mj_attributes.rb +0 -3
- data/lib/emjay/components/head/mj_breakpoint.rb +0 -3
- data/lib/emjay/components/head/mj_font.rb +0 -3
- data/lib/emjay/components/head/mj_head.rb +0 -3
- data/lib/emjay/components/head/mj_html_attributes.rb +0 -3
- data/lib/emjay/components/head/mj_preview.rb +0 -3
- data/lib/emjay/components/head/mj_style.rb +0 -3
- data/lib/emjay/components/head/mj_title.rb +0 -3
- data/lib/emjay/rails/mail_interceptor.rb +8 -2
- data/lib/emjay/rails/template_handler.rb +6 -2
- data/lib/emjay/railtie.rb +12 -2
- data/lib/emjay/registry.rb +70 -6
- data/lib/emjay/renderer.rb +70 -49
- data/lib/emjay/skeleton.rb +15 -6
- data/lib/emjay/version.rb +1 -1
- data/lib/emjay.rb +3 -34
- metadata +16 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 12fae46a93b64e1980adc9085d568a1d337f13d9d8562d94937b7f24f861f0cb
|
|
4
|
+
data.tar.gz: f0db6163d0a00288d62c2b2b2820185f0bbfb7853ffc4b8c726c117d37e3d7e5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 754f37c1091076bba65cff5de501bce2e8f7b138f872e870c3cca2d4819c45e657acd0b4fe0c449503b7a1ed60927303665ed1a6316db37c57e4c8ed4ba2cff7
|
|
7
|
+
data.tar.gz: fda91c57c46af356ee1184c8408f32c1c4caffe1db3fd2f617490ec721929dab2b9921c16f510db32ce75ac68ca61efe497114b9ab28984c8421e3d99a577a7d
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../../body_component"
|
|
4
|
-
require_relative "../../registry"
|
|
5
4
|
|
|
6
5
|
module Emjay
|
|
7
6
|
module Components
|
|
@@ -94,6 +93,4 @@ module Emjay
|
|
|
94
93
|
end
|
|
95
94
|
end
|
|
96
95
|
end
|
|
97
|
-
|
|
98
|
-
Registry.register(Components::MjAccordion)
|
|
99
96
|
end
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../../body_component"
|
|
4
|
-
require_relative "../../registry"
|
|
5
4
|
require_relative "../../helpers/conditional_tag"
|
|
6
5
|
|
|
7
6
|
module Emjay
|
|
@@ -122,6 +121,4 @@ module Emjay
|
|
|
122
121
|
end
|
|
123
122
|
end
|
|
124
123
|
end
|
|
125
|
-
|
|
126
|
-
Registry.register(Components::MjAccordionElement)
|
|
127
124
|
end
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../../body_component"
|
|
4
|
-
require_relative "../../registry"
|
|
5
4
|
|
|
6
5
|
module Emjay
|
|
7
6
|
module Components
|
|
@@ -118,6 +117,4 @@ module Emjay
|
|
|
118
117
|
end
|
|
119
118
|
end
|
|
120
119
|
end
|
|
121
|
-
|
|
122
|
-
Registry.register(Components::MjAccordionText)
|
|
123
120
|
end
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../../body_component"
|
|
4
|
-
require_relative "../../registry"
|
|
5
4
|
require_relative "../../helpers/conditional_tag"
|
|
6
5
|
|
|
7
6
|
module Emjay
|
|
@@ -166,6 +165,4 @@ module Emjay
|
|
|
166
165
|
end
|
|
167
166
|
end
|
|
168
167
|
end
|
|
169
|
-
|
|
170
|
-
Registry.register(Components::MjAccordionTitle)
|
|
171
168
|
end
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../../body_component"
|
|
4
|
-
require_relative "../../registry"
|
|
5
4
|
require_relative "../../helpers/width_parser"
|
|
6
5
|
|
|
7
6
|
module Emjay
|
|
@@ -193,6 +192,4 @@ module Emjay
|
|
|
193
192
|
end
|
|
194
193
|
end
|
|
195
194
|
end
|
|
196
|
-
|
|
197
|
-
Registry.register(Components::MjButton)
|
|
198
195
|
end
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../../body_component"
|
|
4
|
-
require_relative "../../registry"
|
|
5
4
|
require_relative "../../helpers/conditional_tag"
|
|
6
5
|
require_relative "../../helpers/gen_random_hex_string"
|
|
7
6
|
|
|
@@ -405,6 +404,4 @@ module Emjay
|
|
|
405
404
|
end
|
|
406
405
|
end
|
|
407
406
|
end
|
|
408
|
-
|
|
409
|
-
Registry.register(Components::MjCarousel)
|
|
410
407
|
end
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../../body_component"
|
|
4
|
-
require_relative "../../registry"
|
|
5
4
|
require_relative "../../helpers/suffix_css_classes"
|
|
6
5
|
|
|
7
6
|
module Emjay
|
|
@@ -183,6 +182,4 @@ module Emjay
|
|
|
183
182
|
end
|
|
184
183
|
end
|
|
185
184
|
end
|
|
186
|
-
|
|
187
|
-
Registry.register(Components::MjCarouselImage)
|
|
188
185
|
end
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../../body_component"
|
|
4
|
-
require_relative "../../registry"
|
|
5
4
|
require_relative "../../helpers/width_parser"
|
|
6
5
|
|
|
7
6
|
module Emjay
|
|
@@ -250,7 +249,7 @@ module Emjay
|
|
|
250
249
|
if unit == "px"
|
|
251
250
|
floor_width = reduced.floor
|
|
252
251
|
fractional = reduced - floor_width
|
|
253
|
-
extra =
|
|
252
|
+
extra = (sibling * fractional).round.clamp(0, sibling)
|
|
254
253
|
{parsed_width: floor_width + ((index < extra) ? 1 : 0), unit: unit}
|
|
255
254
|
else
|
|
256
255
|
{parsed_width: reduced, unit: unit}
|
|
@@ -425,6 +424,4 @@ module Emjay
|
|
|
425
424
|
end
|
|
426
425
|
end
|
|
427
426
|
end
|
|
428
|
-
|
|
429
|
-
Registry.register(Components::MjColumn)
|
|
430
427
|
end
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../../body_component"
|
|
4
|
-
require_relative "../../registry"
|
|
5
4
|
require_relative "../../helpers/width_parser"
|
|
6
5
|
|
|
7
6
|
module Emjay
|
|
@@ -115,6 +114,4 @@ module Emjay
|
|
|
115
114
|
end
|
|
116
115
|
end
|
|
117
116
|
end
|
|
118
|
-
|
|
119
|
-
Registry.register(Components::MjDivider)
|
|
120
117
|
end
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../../body_component"
|
|
4
|
-
require_relative "../../registry"
|
|
5
4
|
require_relative "../../helpers/width_parser"
|
|
6
5
|
|
|
7
6
|
module Emjay
|
|
@@ -191,6 +190,4 @@ module Emjay
|
|
|
191
190
|
end
|
|
192
191
|
end
|
|
193
192
|
end
|
|
194
|
-
|
|
195
|
-
Registry.register(Components::MjGroup)
|
|
196
193
|
end
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../../body_component"
|
|
4
|
-
require_relative "../../registry"
|
|
5
4
|
require_relative "../../helpers/width_parser"
|
|
6
5
|
|
|
7
6
|
module Emjay
|
|
@@ -377,6 +376,4 @@ module Emjay
|
|
|
377
376
|
end
|
|
378
377
|
end
|
|
379
378
|
end
|
|
380
|
-
|
|
381
|
-
Registry.register(Components::MjHero)
|
|
382
379
|
end
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../../body_component"
|
|
4
|
-
require_relative "../../registry"
|
|
5
4
|
require_relative "../../helpers/width_parser"
|
|
6
5
|
require_relative "../../helpers/make_lower_breakpoint"
|
|
7
6
|
|
|
@@ -183,6 +182,4 @@ module Emjay
|
|
|
183
182
|
end
|
|
184
183
|
end
|
|
185
184
|
end
|
|
186
|
-
|
|
187
|
-
Registry.register(Components::MjImage)
|
|
188
185
|
end
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../../body_component"
|
|
4
|
-
require_relative "../../registry"
|
|
5
4
|
require_relative "../../helpers/conditional_tag"
|
|
6
5
|
require_relative "../../helpers/make_lower_breakpoint"
|
|
7
6
|
require_relative "../../helpers/gen_random_hex_string"
|
|
@@ -182,6 +181,4 @@ module Emjay
|
|
|
182
181
|
end
|
|
183
182
|
end
|
|
184
183
|
end
|
|
185
|
-
|
|
186
|
-
Registry.register(Components::MjNavbar)
|
|
187
184
|
end
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../../body_component"
|
|
4
|
-
require_relative "../../registry"
|
|
5
4
|
require_relative "../../helpers/conditional_tag"
|
|
6
5
|
require_relative "../../helpers/suffix_css_classes"
|
|
7
6
|
|
|
@@ -124,6 +123,4 @@ module Emjay
|
|
|
124
123
|
end
|
|
125
124
|
end
|
|
126
125
|
end
|
|
127
|
-
|
|
128
|
-
Registry.register(Components::MjNavbarLink)
|
|
129
126
|
end
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../../body_component"
|
|
4
|
-
require_relative "../../registry"
|
|
5
4
|
require_relative "../../helpers/suffix_css_classes"
|
|
6
5
|
|
|
7
6
|
module Emjay
|
|
@@ -440,6 +439,4 @@ module Emjay
|
|
|
440
439
|
end
|
|
441
440
|
end
|
|
442
441
|
end
|
|
443
|
-
|
|
444
|
-
Registry.register(Components::MjSection)
|
|
445
442
|
end
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../../body_component"
|
|
4
|
-
require_relative "../../registry"
|
|
5
4
|
|
|
6
5
|
module Emjay
|
|
7
6
|
module Components
|
|
@@ -169,6 +168,4 @@ module Emjay
|
|
|
169
168
|
end
|
|
170
169
|
end
|
|
171
170
|
end
|
|
172
|
-
|
|
173
|
-
Registry.register(Components::MjSocial)
|
|
174
171
|
end
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../../body_component"
|
|
4
|
-
require_relative "../../registry"
|
|
5
4
|
|
|
6
5
|
module Emjay
|
|
7
6
|
module Components
|
|
@@ -270,6 +269,4 @@ module Emjay
|
|
|
270
269
|
end
|
|
271
270
|
end
|
|
272
271
|
end
|
|
273
|
-
|
|
274
|
-
Registry.register(Components::MjSocialElement)
|
|
275
272
|
end
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../../body_component"
|
|
4
|
-
require_relative "../../registry"
|
|
5
4
|
require_relative "../../helpers/width_parser"
|
|
6
5
|
|
|
7
6
|
module Emjay
|
|
@@ -108,6 +107,4 @@ module Emjay
|
|
|
108
107
|
end
|
|
109
108
|
end
|
|
110
109
|
end
|
|
111
|
-
|
|
112
|
-
Registry.register(Components::MjTable)
|
|
113
110
|
end
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../../body_component"
|
|
4
|
-
require_relative "../../registry"
|
|
5
4
|
require_relative "../../helpers/conditional_tag"
|
|
6
5
|
|
|
7
6
|
module Emjay
|
|
@@ -95,6 +94,4 @@ module Emjay
|
|
|
95
94
|
end
|
|
96
95
|
end
|
|
97
96
|
end
|
|
98
|
-
|
|
99
|
-
Registry.register(Components::MjText)
|
|
100
97
|
end
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../../head_component"
|
|
4
|
-
require_relative "../../registry"
|
|
5
4
|
|
|
6
5
|
module Emjay
|
|
7
6
|
module Components
|
|
@@ -33,6 +32,4 @@ module Emjay
|
|
|
33
32
|
end
|
|
34
33
|
end
|
|
35
34
|
end
|
|
36
|
-
|
|
37
|
-
Registry.register(Components::MjAttributes)
|
|
38
35
|
end
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../../head_component"
|
|
4
|
-
require_relative "../../registry"
|
|
5
4
|
|
|
6
5
|
module Emjay
|
|
7
6
|
module Components
|
|
@@ -23,6 +22,4 @@ module Emjay
|
|
|
23
22
|
end
|
|
24
23
|
end
|
|
25
24
|
end
|
|
26
|
-
|
|
27
|
-
Registry.register(Components::MjBreakpoint)
|
|
28
25
|
end
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../../head_component"
|
|
4
|
-
require_relative "../../registry"
|
|
5
4
|
|
|
6
5
|
module Emjay
|
|
7
6
|
module Components
|
|
@@ -28,6 +27,4 @@ module Emjay
|
|
|
28
27
|
end
|
|
29
28
|
end
|
|
30
29
|
end
|
|
31
|
-
|
|
32
|
-
Registry.register(Components::MjHtmlAttributes)
|
|
33
30
|
end
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../../head_component"
|
|
4
|
-
require_relative "../../registry"
|
|
5
4
|
|
|
6
5
|
module Emjay
|
|
7
6
|
module Components
|
|
@@ -19,6 +18,4 @@ module Emjay
|
|
|
19
18
|
end
|
|
20
19
|
end
|
|
21
20
|
end
|
|
22
|
-
|
|
23
|
-
Registry.register(Components::MjPreview)
|
|
24
21
|
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "measurometer"
|
|
4
|
+
|
|
3
5
|
module Emjay
|
|
4
6
|
module Rails
|
|
5
7
|
class MailInterceptor
|
|
@@ -14,6 +16,12 @@ module Emjay
|
|
|
14
16
|
end
|
|
15
17
|
|
|
16
18
|
def compile_mjml!(message)
|
|
19
|
+
Measurometer.instrument("emjay.mail_interceptor.compile") { compile_message!(message) }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def compile_message!(message)
|
|
17
25
|
if message.multipart?
|
|
18
26
|
message.parts.each { |part| compile_part!(part) }
|
|
19
27
|
else
|
|
@@ -21,8 +29,6 @@ module Emjay
|
|
|
21
29
|
end
|
|
22
30
|
end
|
|
23
31
|
|
|
24
|
-
private
|
|
25
|
-
|
|
26
32
|
def compile_part!(part)
|
|
27
33
|
if part.multipart?
|
|
28
34
|
part.parts.each { |p| compile_part!(p) }
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "measurometer"
|
|
4
|
+
|
|
3
5
|
module Emjay
|
|
4
6
|
module Rails
|
|
5
7
|
module TemplateHandler
|
|
@@ -8,8 +10,10 @@ module Emjay
|
|
|
8
10
|
# template extension with ERB support. MJML → HTML compilation
|
|
9
11
|
# happens later via Emjay::Rails::MailInterceptor, after Rails
|
|
10
12
|
# has assembled the full render (template + layout).
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
Measurometer.instrument("emjay.template_handler.compile") do
|
|
14
|
+
erb_handler = ActionView::Template.registered_template_handler(:erb)
|
|
15
|
+
erb_handler.call(template, source)
|
|
16
|
+
end
|
|
13
17
|
end
|
|
14
18
|
end
|
|
15
19
|
end
|
data/lib/emjay/railtie.rb
CHANGED
|
@@ -2,11 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
module Emjay
|
|
4
4
|
class Railtie < Rails::Railtie
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
# Register the template handler on :action_view load, not :action_mailer.
|
|
6
|
+
# ActionView's FileSystemResolver builds its PathParser regex from the
|
|
7
|
+
# registered extensions on first template lookup; if ActionView loads
|
|
8
|
+
# before ActionMailer (e.g. a controller renders a view first), the
|
|
9
|
+
# regex is cached without :mjml and .html.mjml templates stay invisible
|
|
10
|
+
# for the rest of the process.
|
|
11
|
+
initializer "emjay.register_template_handler" do
|
|
12
|
+
ActiveSupport.on_load(:action_view) do
|
|
7
13
|
require "emjay/rails/template_handler"
|
|
8
14
|
ActionView::Template.register_template_handler(:mjml, Emjay::Rails::TemplateHandler)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
9
17
|
|
|
18
|
+
initializer "emjay.register_mail_interceptor" do
|
|
19
|
+
ActiveSupport.on_load(:action_mailer) do
|
|
10
20
|
require "emjay/rails/mail_interceptor"
|
|
11
21
|
interceptor = Emjay::Rails::MailInterceptor
|
|
12
22
|
ActionMailer::Base.register_interceptor(interceptor)
|
data/lib/emjay/registry.rb
CHANGED
|
@@ -1,19 +1,83 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "measurometer"
|
|
4
|
+
|
|
3
5
|
module Emjay
|
|
6
|
+
# Lazy autoloader for MJML components. Scans `lib/emjay/components/**/*.rb`
|
|
7
|
+
# once at load time, then derives both the MJML tag name and the Ruby class
|
|
8
|
+
# from each filename:
|
|
9
|
+
#
|
|
10
|
+
# components/body/mj_button.rb → tag "mj-button" → Emjay::Components::MjButton
|
|
11
|
+
#
|
|
12
|
+
# Files are `require`d and constants resolved on first `find`.
|
|
4
13
|
module Registry
|
|
5
|
-
|
|
14
|
+
class UnknownComponent < StandardError; end
|
|
6
15
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
16
|
+
COMPONENTS_ROOT = File.expand_path("components", __dir__)
|
|
17
|
+
|
|
18
|
+
PATHS = Dir.glob("{head,body}/*.rb", base: COMPONENTS_ROOT).sort.each_with_object({}) do |rel, h|
|
|
19
|
+
basename = File.basename(rel, ".rb") # e.g. "mj_html_attributes"
|
|
20
|
+
tag = basename.tr("_", "-") # → "mj-html-attributes"
|
|
21
|
+
h[tag] = File.join(COMPONENTS_ROOT, rel)
|
|
22
|
+
end.freeze
|
|
23
|
+
|
|
24
|
+
@loaded = {}
|
|
25
|
+
@mutex = Mutex.new
|
|
10
26
|
|
|
11
27
|
def self.find(tag_name)
|
|
12
|
-
@
|
|
28
|
+
@loaded[tag_name] ||= load_component(tag_name)
|
|
13
29
|
end
|
|
14
30
|
|
|
15
31
|
def self.components
|
|
16
|
-
@components
|
|
32
|
+
@components ||= LazyComponents.new
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Kept for backwards compatibility with callers that eagerly register.
|
|
36
|
+
def self.register(component_class)
|
|
37
|
+
@loaded[component_class.component_name] = component_class
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.load_component(tag_name)
|
|
41
|
+
path = PATHS[tag_name] or return nil
|
|
42
|
+
@mutex.synchronize do
|
|
43
|
+
return @loaded[tag_name] if @loaded[tag_name]
|
|
44
|
+
Measurometer.instrument("emjay.registry.load_component.#{tag_name}") do
|
|
45
|
+
require path
|
|
46
|
+
const_name = File.basename(path, ".rb").split("_").map(&:capitalize).join
|
|
47
|
+
Emjay::Components.const_get(const_name)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# The renderer treats `Registry.components` as a hash keyed by tag name.
|
|
53
|
+
# This proxy resolves each lookup through `Registry.find` so components
|
|
54
|
+
# only load when actually referenced.
|
|
55
|
+
class LazyComponents
|
|
56
|
+
include Enumerable
|
|
57
|
+
|
|
58
|
+
def [](tag_name)
|
|
59
|
+
component = Registry.find(tag_name)
|
|
60
|
+
# We only reach here on render paths (never during parsing, which uses
|
|
61
|
+
# Registry.find directly and tolerates nil). If a tag looks like MJML
|
|
62
|
+
# but resolves to nothing, it's almost certainly a typo like
|
|
63
|
+
# `<mj-nonesuch>` — surface it instead of silently rendering blank.
|
|
64
|
+
if component.nil? && tag_name.to_s.start_with?("mj-")
|
|
65
|
+
raise UnknownComponent, "Unknown MJML component #{tag_name.inspect}"
|
|
66
|
+
end
|
|
67
|
+
component
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def each
|
|
71
|
+
PATHS.each_key { |tag| yield tag, Registry.find(tag) }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def keys
|
|
75
|
+
PATHS.keys
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def values
|
|
79
|
+
PATHS.each_key.map { |tag| Registry.find(tag) }
|
|
80
|
+
end
|
|
17
81
|
end
|
|
18
82
|
end
|
|
19
83
|
end
|
data/lib/emjay/renderer.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "nokogiri"
|
|
4
|
+
require "measurometer"
|
|
4
5
|
require_relative "global_data"
|
|
5
6
|
require_relative "registry"
|
|
6
7
|
require_relative "skeleton"
|
|
@@ -13,15 +14,21 @@ module Emjay
|
|
|
13
14
|
VOID_ELEMENTS_RE = /(<(?:br|hr|img|input|meta|link|area|base|col|embed|param|source|track|wbr)(?:\s[^>]*)?)>/i
|
|
14
15
|
|
|
15
16
|
def self.call(mjml_string, options = {})
|
|
17
|
+
Measurometer.instrument("emjay.render") { render(mjml_string, options) }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.render(mjml_string, options = {})
|
|
16
21
|
# 1. Pre-process and parse MJML string with Nokogiri::XML.
|
|
17
22
|
# Two fixups are needed to bridge MJML (which embeds HTML content) and XML:
|
|
18
23
|
# a) Convert HTML void elements to self-closing (e.g. <br> → <br/>)
|
|
19
24
|
# so XML parsing doesn't treat them as unclosed tags
|
|
20
25
|
# b) Escape bare < characters from template syntax in mj-raw
|
|
21
26
|
# (e.g. { if item < 5 }) so they don't break XML parsing
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
doc = Measurometer.instrument("emjay.parse") do
|
|
28
|
+
preprocessed = mjml_string.gsub(VOID_ELEMENTS_RE, '\1/>')
|
|
29
|
+
preprocessed = preprocessed.gsub(/<(?![a-zA-Z\/!?])/, RAW_LT_PLACEHOLDER)
|
|
30
|
+
Nokogiri::XML(preprocessed)
|
|
31
|
+
end
|
|
25
32
|
mjml_root = doc.at_xpath("//mjml") || doc.root
|
|
26
33
|
|
|
27
34
|
# 2. Build GlobalData
|
|
@@ -79,9 +86,11 @@ module Emjay
|
|
|
79
86
|
|
|
80
87
|
# Process head
|
|
81
88
|
if mj_head_el
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
89
|
+
Measurometer.instrument("emjay.process_head") do
|
|
90
|
+
head_node = Measurometer.instrument("emjay.to_hash.head") { nokogiri_to_hash(mj_head_el) }
|
|
91
|
+
head_result = processing.call(head_node, head_helpers)
|
|
92
|
+
global_data.head_raw = head_result if head_result.is_a?(Array)
|
|
93
|
+
end
|
|
85
94
|
end
|
|
86
95
|
|
|
87
96
|
# Body helpers context
|
|
@@ -113,15 +122,17 @@ module Emjay
|
|
|
113
122
|
# Process body
|
|
114
123
|
content = nil
|
|
115
124
|
if mj_body_el
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
125
|
+
content = Measurometer.instrument("emjay.process_body") do
|
|
126
|
+
body_node = Measurometer.instrument("emjay.to_hash.body") { nokogiri_to_hash(mj_body_el) }
|
|
127
|
+
body_node = Measurometer.instrument("emjay.apply_attributes") { apply_attributes.call(body_node) }
|
|
128
|
+
processing.call(body_node, body_helpers)
|
|
129
|
+
end
|
|
119
130
|
end
|
|
120
131
|
|
|
121
132
|
raise "Malformed MJML. Check that your structure is correct and enclosed in <mjml> tags." unless content
|
|
122
133
|
|
|
123
134
|
# Minify outlook conditionals
|
|
124
|
-
content = MinifyOutlookConditionals.call(content)
|
|
135
|
+
content = Measurometer.instrument("emjay.minify_outlook_conditionals") { MinifyOutlookConditionals.call(content) }
|
|
125
136
|
|
|
126
137
|
# Handle mj-raw outside body (before-doctype)
|
|
127
138
|
mjml_root&.xpath("mj-raw")&.each do |raw_el|
|
|
@@ -136,44 +147,50 @@ module Emjay
|
|
|
136
147
|
# syntax (mj-raw) with placeholders so XML parsing succeeds. Use custom
|
|
137
148
|
# serializer so text nodes (including > in templates) pass through raw.
|
|
138
149
|
unless global_data.html_attributes.empty?
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
150
|
+
content = Measurometer.instrument("emjay.html_attributes") do
|
|
151
|
+
escaped = content.gsub(/<(?![a-zA-Z\/!?])/, RAW_LT_PLACEHOLDER)
|
|
152
|
+
content_doc = Nokogiri::XML.fragment(escaped)
|
|
153
|
+
global_data.html_attributes.each do |selector, data|
|
|
154
|
+
content_doc.css(selector).each do |node|
|
|
155
|
+
data.each do |attr_name, value|
|
|
156
|
+
node[attr_name] = value || ""
|
|
157
|
+
end
|
|
145
158
|
end
|
|
146
159
|
end
|
|
160
|
+
serialize_fragment(content_doc).gsub(RAW_LT_PLACEHOLDER, "<")
|
|
147
161
|
end
|
|
148
|
-
content = serialize_fragment(content_doc).gsub(RAW_LT_PLACEHOLDER, "<")
|
|
149
162
|
end
|
|
150
163
|
|
|
151
164
|
# Wrap in skeleton
|
|
152
|
-
content =
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
165
|
+
content = Measurometer.instrument("emjay.skeleton") do
|
|
166
|
+
Skeleton.call(
|
|
167
|
+
before_doctype: global_data.before_doctype,
|
|
168
|
+
breakpoint: global_data.breakpoint,
|
|
169
|
+
content: content,
|
|
170
|
+
fonts: global_data.fonts,
|
|
171
|
+
media_queries: global_data.media_queries,
|
|
172
|
+
head_style: global_data.head_style,
|
|
173
|
+
components_head_style: global_data.components_head_style,
|
|
174
|
+
head_raw: global_data.head_raw.is_a?(Array) ? global_data.head_raw : [],
|
|
175
|
+
title: global_data.title,
|
|
176
|
+
style: global_data.style,
|
|
177
|
+
force_owa_desktop: global_data.force_owa_desktop,
|
|
178
|
+
printer_support: options[:printer_support] || false,
|
|
179
|
+
inline_style: global_data.inline_style,
|
|
180
|
+
lang: global_data.lang,
|
|
181
|
+
dir: global_data.dir
|
|
182
|
+
)
|
|
183
|
+
end
|
|
169
184
|
|
|
170
185
|
# CSS inlining for <mj-style inline="inline">
|
|
171
186
|
if global_data.inline_style.any?
|
|
172
|
-
content =
|
|
187
|
+
content = Measurometer.instrument("emjay.inline_css") do
|
|
188
|
+
inline_css(content, global_data.inline_style.join("\n"))
|
|
189
|
+
end
|
|
173
190
|
end
|
|
174
191
|
|
|
175
192
|
# Merge outlook conditionals
|
|
176
|
-
MergeOutlookConditionals.call(content)
|
|
193
|
+
Measurometer.instrument("emjay.merge_outlook_conditionals") { MergeOutlookConditionals.call(content) }
|
|
177
194
|
end
|
|
178
195
|
|
|
179
196
|
# Converts a Nokogiri element to a hash matching the JS parsed format
|
|
@@ -185,9 +202,11 @@ module Emjay
|
|
|
185
202
|
|
|
186
203
|
children = element.children.select(&:element?).map { |child| nokogiri_to_hash(child) }
|
|
187
204
|
|
|
188
|
-
# For ending-tag components, content is the inner HTML
|
|
205
|
+
# For ending-tag components, content is the inner HTML. Only MJML tags
|
|
206
|
+
# can be components — plain HTML (e.g. <a>, <b> nested in mj-text) never
|
|
207
|
+
# is, so don't waste a Registry lookup on it.
|
|
189
208
|
tag_name = element.name
|
|
190
|
-
component_class = Registry.find(tag_name)
|
|
209
|
+
component_class = tag_name.start_with?("mj-") ? Registry.find(tag_name) : nil
|
|
191
210
|
content = if component_class&.ending_tag?
|
|
192
211
|
inner = element.inner_html.strip
|
|
193
212
|
# Restore escaped bare < characters (from template syntax in mj-raw etc.)
|
|
@@ -257,16 +276,18 @@ module Emjay
|
|
|
257
276
|
def self.inline_css(html, extra_css)
|
|
258
277
|
require "premailer"
|
|
259
278
|
|
|
260
|
-
premailer =
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
279
|
+
premailer = Measurometer.instrument("emjay.inline_css.premailer_parse") do
|
|
280
|
+
Premailer.new(
|
|
281
|
+
html,
|
|
282
|
+
with_html_string: true,
|
|
283
|
+
css_string: extra_css,
|
|
284
|
+
include_style_tags: false,
|
|
285
|
+
include_link_tags: false,
|
|
286
|
+
preserve_styles: true,
|
|
287
|
+
adapter: :nokogiri
|
|
288
|
+
)
|
|
289
|
+
end
|
|
290
|
+
Measurometer.instrument("emjay.inline_css.to_inline_css") { premailer.to_inline_css }
|
|
270
291
|
end
|
|
271
292
|
|
|
272
293
|
RAW_LT_PLACEHOLDER = "___MJML_RAW_LT___"
|
|
@@ -299,7 +320,7 @@ module Emjay
|
|
|
299
320
|
end
|
|
300
321
|
end
|
|
301
322
|
|
|
302
|
-
private_class_method :nokogiri_to_hash, :apply_attributes_fn, :inline_css,
|
|
323
|
+
private_class_method :render, :nokogiri_to_hash, :apply_attributes_fn, :inline_css,
|
|
303
324
|
:serialize_fragment, :serialize_node
|
|
304
325
|
end
|
|
305
326
|
end
|
data/lib/emjay/skeleton.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "measurometer"
|
|
3
4
|
require_relative "helpers/fonts"
|
|
4
5
|
require_relative "helpers/media_queries"
|
|
5
6
|
require_relative "helpers/styles"
|
|
@@ -26,12 +27,20 @@ module Emjay
|
|
|
26
27
|
|
|
27
28
|
before_doctype_str = before_doctype.empty? ? "" : "#{before_doctype}\n"
|
|
28
29
|
|
|
29
|
-
fonts_tags =
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
fonts_tags = Measurometer.instrument("emjay.skeleton.fonts") do
|
|
31
|
+
Fonts.build_tags(content, inline_style, fonts)
|
|
32
|
+
end
|
|
33
|
+
media_query_tags = Measurometer.instrument("emjay.skeleton.media_queries") do
|
|
34
|
+
MediaQueries.build_tags(breakpoint, media_queries,
|
|
35
|
+
force_owa_desktop: force_owa_desktop,
|
|
36
|
+
printer_support: printer_support)
|
|
37
|
+
end
|
|
38
|
+
component_styles = Measurometer.instrument("emjay.skeleton.component_styles") do
|
|
39
|
+
Styles.build_from_components(breakpoint, components_head_style, head_style)
|
|
40
|
+
end
|
|
41
|
+
tag_styles = Measurometer.instrument("emjay.skeleton.tag_styles") do
|
|
42
|
+
Styles.build_from_tags(breakpoint, style)
|
|
43
|
+
end
|
|
35
44
|
raw = head_raw.compact.join("\n")
|
|
36
45
|
|
|
37
46
|
<<~HTML
|
data/lib/emjay/version.rb
CHANGED
data/lib/emjay.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "measurometer"
|
|
4
|
+
|
|
3
5
|
require_relative "emjay/version"
|
|
4
6
|
require_relative "emjay/registry"
|
|
5
7
|
require_relative "emjay/component"
|
|
@@ -22,40 +24,7 @@ require_relative "emjay/helpers/styles"
|
|
|
22
24
|
require_relative "emjay/helpers/make_lower_breakpoint"
|
|
23
25
|
require_relative "emjay/helpers/gen_random_hex_string"
|
|
24
26
|
|
|
25
|
-
#
|
|
26
|
-
require_relative "emjay/components/head/mj_head"
|
|
27
|
-
require_relative "emjay/components/head/mj_attributes"
|
|
28
|
-
require_relative "emjay/components/head/mj_style"
|
|
29
|
-
require_relative "emjay/components/head/mj_font"
|
|
30
|
-
require_relative "emjay/components/head/mj_title"
|
|
31
|
-
require_relative "emjay/components/head/mj_preview"
|
|
32
|
-
require_relative "emjay/components/head/mj_breakpoint"
|
|
33
|
-
require_relative "emjay/components/head/mj_html_attributes"
|
|
34
|
-
|
|
35
|
-
# Body components
|
|
36
|
-
require_relative "emjay/components/body/mj_body"
|
|
37
|
-
require_relative "emjay/components/body/mj_section"
|
|
38
|
-
require_relative "emjay/components/body/mj_column"
|
|
39
|
-
require_relative "emjay/components/body/mj_text"
|
|
40
|
-
require_relative "emjay/components/body/mj_wrapper"
|
|
41
|
-
require_relative "emjay/components/body/mj_group"
|
|
42
|
-
require_relative "emjay/components/body/mj_image"
|
|
43
|
-
require_relative "emjay/components/body/mj_button"
|
|
44
|
-
require_relative "emjay/components/body/mj_divider"
|
|
45
|
-
require_relative "emjay/components/body/mj_spacer"
|
|
46
|
-
require_relative "emjay/components/body/mj_table"
|
|
47
|
-
require_relative "emjay/components/body/mj_raw"
|
|
48
|
-
require_relative "emjay/components/body/mj_hero"
|
|
49
|
-
require_relative "emjay/components/body/mj_social"
|
|
50
|
-
require_relative "emjay/components/body/mj_social_element"
|
|
51
|
-
require_relative "emjay/components/body/mj_navbar"
|
|
52
|
-
require_relative "emjay/components/body/mj_navbar_link"
|
|
53
|
-
require_relative "emjay/components/body/mj_accordion"
|
|
54
|
-
require_relative "emjay/components/body/mj_accordion_element"
|
|
55
|
-
require_relative "emjay/components/body/mj_accordion_title"
|
|
56
|
-
require_relative "emjay/components/body/mj_accordion_text"
|
|
57
|
-
require_relative "emjay/components/body/mj_carousel"
|
|
58
|
-
require_relative "emjay/components/body/mj_carousel_image"
|
|
27
|
+
# Components are autoloaded on demand via Emjay::Registry — see registry.rb.
|
|
59
28
|
|
|
60
29
|
module Emjay
|
|
61
30
|
def self.to_html(mjml_string, options = {})
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: emjay
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Julik Tarkhanov
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-
|
|
10
|
+
date: 2026-09-16 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: nokogiri
|
|
@@ -37,6 +37,20 @@ dependencies:
|
|
|
37
37
|
- - ">="
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
39
|
version: '0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: measurometer
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '1.3'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '1.3'
|
|
40
54
|
description: Converts MJML email markup to responsive HTML — no Node.js, no native
|
|
41
55
|
extensions, no shelling out.
|
|
42
56
|
email:
|