emjay 0.1.0

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.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +158 -0
  4. data/lib/emjay/body_component.rb +142 -0
  5. data/lib/emjay/component.rb +61 -0
  6. data/lib/emjay/components/body/mj_accordion.rb +99 -0
  7. data/lib/emjay/components/body/mj_accordion_element.rb +127 -0
  8. data/lib/emjay/components/body/mj_accordion_text.rb +123 -0
  9. data/lib/emjay/components/body/mj_accordion_title.rb +171 -0
  10. data/lib/emjay/components/body/mj_body.rb +70 -0
  11. data/lib/emjay/components/body/mj_button.rb +198 -0
  12. data/lib/emjay/components/body/mj_carousel.rb +410 -0
  13. data/lib/emjay/components/body/mj_carousel_image.rb +188 -0
  14. data/lib/emjay/components/body/mj_column.rb +287 -0
  15. data/lib/emjay/components/body/mj_divider.rb +120 -0
  16. data/lib/emjay/components/body/mj_group.rb +196 -0
  17. data/lib/emjay/components/body/mj_hero.rb +382 -0
  18. data/lib/emjay/components/body/mj_image.rb +188 -0
  19. data/lib/emjay/components/body/mj_navbar.rb +187 -0
  20. data/lib/emjay/components/body/mj_navbar_link.rb +129 -0
  21. data/lib/emjay/components/body/mj_raw.rb +34 -0
  22. data/lib/emjay/components/body/mj_section.rb +442 -0
  23. data/lib/emjay/components/body/mj_social.rb +174 -0
  24. data/lib/emjay/components/body/mj_social_element.rb +272 -0
  25. data/lib/emjay/components/body/mj_spacer.rb +57 -0
  26. data/lib/emjay/components/body/mj_table.rb +113 -0
  27. data/lib/emjay/components/body/mj_text.rb +100 -0
  28. data/lib/emjay/components/body/mj_wrapper.rb +56 -0
  29. data/lib/emjay/components/head/mj_attributes.rb +38 -0
  30. data/lib/emjay/components/head/mj_breakpoint.rb +28 -0
  31. data/lib/emjay/components/head/mj_font.rb +24 -0
  32. data/lib/emjay/components/head/mj_head.rb +20 -0
  33. data/lib/emjay/components/head/mj_html_attributes.rb +33 -0
  34. data/lib/emjay/components/head/mj_preview.rb +24 -0
  35. data/lib/emjay/components/head/mj_style.rb +34 -0
  36. data/lib/emjay/components/head/mj_title.rb +24 -0
  37. data/lib/emjay/global_data.rb +64 -0
  38. data/lib/emjay/head_component.rb +37 -0
  39. data/lib/emjay/helpers/conditional_tag.rb +24 -0
  40. data/lib/emjay/helpers/fonts.rb +34 -0
  41. data/lib/emjay/helpers/gen_random_hex_string.rb +9 -0
  42. data/lib/emjay/helpers/make_lower_breakpoint.rb +17 -0
  43. data/lib/emjay/helpers/media_queries.rb +47 -0
  44. data/lib/emjay/helpers/merge_outlook_conditionals.rb +11 -0
  45. data/lib/emjay/helpers/minify_outlook_conditionals.rb +18 -0
  46. data/lib/emjay/helpers/shorthand_parser.rb +33 -0
  47. data/lib/emjay/helpers/styles.rb +34 -0
  48. data/lib/emjay/helpers/suffix_css_classes.rb +12 -0
  49. data/lib/emjay/helpers/width_parser.rb +26 -0
  50. data/lib/emjay/rails/mail_interceptor.rb +37 -0
  51. data/lib/emjay/rails/template_handler.rb +16 -0
  52. data/lib/emjay/railtie.rb +21 -0
  53. data/lib/emjay/registry.rb +19 -0
  54. data/lib/emjay/renderer.rb +302 -0
  55. data/lib/emjay/skeleton.rb +80 -0
  56. data/lib/emjay/version.rb +5 -0
  57. data/lib/emjay.rb +66 -0
  58. data/llms.txt +130 -0
  59. metadata +129 -0
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../body_component"
4
+ require_relative "../../registry"
5
+
6
+ module Emjay
7
+ module Components
8
+ class MjAccordionText < BodyComponent
9
+ def self.component_name
10
+ "mj-accordion-text"
11
+ end
12
+
13
+ def self.ending_tag?
14
+ true
15
+ end
16
+
17
+ def self.default_attributes
18
+ {
19
+ "font-size" => "13px",
20
+ "line-height" => "1",
21
+ "padding" => "16px"
22
+ }
23
+ end
24
+
25
+ def self.allowed_attributes
26
+ {
27
+ "background-color" => "color",
28
+ "font-size" => "unit(px)",
29
+ "font-family" => "string",
30
+ "font-weight" => "string",
31
+ "letter-spacing" => "unitWithNegative(px,em)",
32
+ "line-height" => "unit(px,%,)",
33
+ "color" => "color",
34
+ "padding-bottom" => "unit(px,%)",
35
+ "padding-left" => "unit(px,%)",
36
+ "padding-right" => "unit(px,%)",
37
+ "padding-top" => "unit(px,%)",
38
+ "padding" => "unit(px,%){1,4}"
39
+ }
40
+ end
41
+
42
+ def get_styles
43
+ {
44
+ td: {
45
+ "background" => get_attribute("background-color"),
46
+ "font-size" => get_attribute("font-size"),
47
+ "font-family" => resolve_font_family,
48
+ "font-weight" => get_attribute("font-weight"),
49
+ "letter-spacing" => get_attribute("letter-spacing"),
50
+ "line-height" => get_attribute("line-height"),
51
+ "color" => get_attribute("color"),
52
+ "padding" => get_attribute("padding"),
53
+ "padding-bottom" => get_attribute("padding-bottom"),
54
+ "padding-left" => get_attribute("padding-left"),
55
+ "padding-right" => get_attribute("padding-right"),
56
+ "padding-top" => get_attribute("padding-top")
57
+ },
58
+ table: {
59
+ "width" => "100%",
60
+ "border-bottom" => get_attribute("border")
61
+ }
62
+ }
63
+ end
64
+
65
+ def render
66
+ content_attrs = html_attributes(class: "mj-accordion-content")
67
+ table_attrs = html_attributes(
68
+ cellspacing: "0",
69
+ cellpadding: "0",
70
+ style: :table
71
+ )
72
+
73
+ <<~HTML
74
+ <div
75
+ #{content_attrs}
76
+ >
77
+ <table
78
+ #{table_attrs}
79
+ >
80
+ <tbody>
81
+ <tr>
82
+ #{render_content}
83
+ </tr>
84
+ </tbody>
85
+ </table>
86
+ </div>
87
+ HTML
88
+ end
89
+
90
+ private
91
+
92
+ def resolve_font_family
93
+ raw_attrs = @props[:raw_attrs] || {}
94
+ if raw_attrs.key?("font-family")
95
+ return get_attribute("font-family")
96
+ end
97
+ if @context[:elementFontFamily]
98
+ return @context[:elementFontFamily]
99
+ end
100
+ if @context[:accordionFontFamily]
101
+ return @context[:accordionFontFamily]
102
+ end
103
+ self.class.default_attributes["font-family"]
104
+ end
105
+
106
+ def render_content
107
+ td_attrs = html_attributes(
108
+ class: get_attribute("css-class"),
109
+ style: :td
110
+ )
111
+ <<~HTML
112
+ <td
113
+ #{td_attrs}
114
+ >
115
+ #{get_content}
116
+ </td>
117
+ HTML
118
+ end
119
+ end
120
+ end
121
+
122
+ Registry.register(Components::MjAccordionText)
123
+ end
@@ -0,0 +1,171 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../body_component"
4
+ require_relative "../../registry"
5
+ require_relative "../../helpers/conditional_tag"
6
+
7
+ module Emjay
8
+ module Components
9
+ class MjAccordionTitle < BodyComponent
10
+ def self.component_name
11
+ "mj-accordion-title"
12
+ end
13
+
14
+ def self.ending_tag?
15
+ true
16
+ end
17
+
18
+ def self.default_attributes
19
+ {
20
+ "font-size" => "13px",
21
+ "padding" => "16px"
22
+ }
23
+ end
24
+
25
+ def self.allowed_attributes
26
+ {
27
+ "background-color" => "color",
28
+ "color" => "color",
29
+ "font-size" => "unit(px)",
30
+ "font-family" => "string",
31
+ "font-weight" => "string",
32
+ "padding-bottom" => "unit(px,%)",
33
+ "padding-left" => "unit(px,%)",
34
+ "padding-right" => "unit(px,%)",
35
+ "padding-top" => "unit(px,%)",
36
+ "padding" => "unit(px,%){1,4}"
37
+ }
38
+ end
39
+
40
+ def get_styles
41
+ {
42
+ td: {
43
+ "width" => "100%",
44
+ "background-color" => get_attribute("background-color"),
45
+ "color" => get_attribute("color"),
46
+ "font-size" => get_attribute("font-size"),
47
+ "font-family" => resolve_font_family,
48
+ "font-weight" => get_attribute("font-weight"),
49
+ "padding" => get_attribute("padding"),
50
+ "padding-bottom" => get_attribute("padding-bottom"),
51
+ "padding-left" => get_attribute("padding-left"),
52
+ "padding-right" => get_attribute("padding-right"),
53
+ "padding-top" => get_attribute("padding-top")
54
+ },
55
+ table: {
56
+ "width" => "100%",
57
+ "border-bottom" => get_attribute("border")
58
+ },
59
+ td2: {
60
+ "padding" => "16px",
61
+ "background" => get_attribute("background-color"),
62
+ "vertical-align" => get_attribute("icon-align")
63
+ },
64
+ img: {
65
+ "display" => "none",
66
+ "width" => get_attribute("icon-width"),
67
+ "height" => get_attribute("icon-height")
68
+ }
69
+ }
70
+ end
71
+
72
+ def render
73
+ content_elements = [render_title, render_icons]
74
+ content = if get_attribute("icon-position") == "right"
75
+ content_elements
76
+ else
77
+ content_elements.reverse
78
+ end.join("\n")
79
+
80
+ table_attrs = html_attributes(
81
+ cellspacing: "0",
82
+ cellpadding: "0",
83
+ style: :table
84
+ )
85
+
86
+ <<~HTML
87
+ <div#{html_attributes(class: "mj-accordion-title")}>
88
+ <table
89
+ #{table_attrs}
90
+ >
91
+ <tbody>
92
+ <tr>
93
+ #{content}
94
+ </tr>
95
+ </tbody>
96
+ </table>
97
+ </div>
98
+ HTML
99
+ end
100
+
101
+ private
102
+
103
+ def resolve_font_family
104
+ raw_attrs = @props[:raw_attrs] || {}
105
+ if raw_attrs.key?("font-family")
106
+ return get_attribute("font-family")
107
+ end
108
+ if @context[:elementFontFamily]
109
+ return @context[:elementFontFamily]
110
+ end
111
+ if @context[:accordionFontFamily]
112
+ return @context[:accordionFontFamily]
113
+ end
114
+ self.class.default_attributes["font-family"]
115
+ end
116
+
117
+ def render_title
118
+ td_attrs = html_attributes(
119
+ class: get_attribute("css-class"),
120
+ style: :td
121
+ )
122
+ <<~HTML
123
+ <td
124
+ #{td_attrs}
125
+ >
126
+ #{get_content}
127
+ </td>
128
+ HTML
129
+ end
130
+
131
+ def render_icons
132
+ td2_attrs = html_attributes(
133
+ class: "mj-accordion-ico",
134
+ style: :td2
135
+ )
136
+
137
+ more_attrs = html_attributes(
138
+ src: get_attribute("icon-wrapped-url"),
139
+ alt: get_attribute("icon-wrapped-alt"),
140
+ class: "mj-accordion-more",
141
+ style: :img
142
+ )
143
+
144
+ less_attrs = html_attributes(
145
+ src: get_attribute("icon-unwrapped-url"),
146
+ alt: get_attribute("icon-unwrapped-alt"),
147
+ class: "mj-accordion-less",
148
+ style: :img
149
+ )
150
+
151
+ ConditionalTag.conditional_tag(
152
+ <<~ICON,
153
+ <td
154
+ #{td2_attrs}
155
+ >
156
+ <img
157
+ #{more_attrs}
158
+ />
159
+ <img
160
+ #{less_attrs}
161
+ />
162
+ </td>
163
+ ICON
164
+ negation: true
165
+ )
166
+ end
167
+ end
168
+ end
169
+
170
+ Registry.register(Components::MjAccordionTitle)
171
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../body_component"
4
+ require_relative "../../registry"
5
+
6
+ module Emjay
7
+ module Components
8
+ class MjBody < BodyComponent
9
+ def self.component_name
10
+ "mj-body"
11
+ end
12
+
13
+ def self.default_attributes
14
+ {"width" => "600px"}
15
+ end
16
+
17
+ def self.allowed_attributes
18
+ {
19
+ "width" => "unit(px)",
20
+ "background-color" => "color",
21
+ "id" => "string"
22
+ }
23
+ end
24
+
25
+ def get_child_context
26
+ @context.merge(container_width: get_attribute("width"))
27
+ end
28
+
29
+ def get_styles
30
+ {
31
+ body: {
32
+ "word-spacing" => "normal",
33
+ "background-color" => get_attribute("background-color")
34
+ },
35
+ div: {
36
+ "word-spacing" => "normal",
37
+ "background-color" => get_attribute("background-color")
38
+ }
39
+ }
40
+ end
41
+
42
+ def render
43
+ global_data = @context[:global_data]
44
+ lang = global_data&.lang || "und"
45
+ dir = global_data&.dir || "auto"
46
+ title = global_data&.title || ""
47
+ preview = global_data&.preview || ""
48
+
49
+ preview_html = if preview.empty?
50
+ ""
51
+ else
52
+ %(\n <div style="display:none;font-size:1px;color:#ffffff;line-height:1px;max-height:0px;max-width:0px;opacity:0;overflow:hidden;">#{preview}</div>\n )
53
+ end
54
+
55
+ title_attr = title.empty? ? "" : " aria-label=\"#{title}\""
56
+
57
+ <<~HTML
58
+ <body#{html_attributes(id: get_attribute("id"), class: get_attribute("css-class"), style: :body)}>
59
+ #{preview_html}
60
+ <div#{title_attr} aria-roledescription="email" role="article" lang="#{lang}" dir="#{dir}"#{html_attributes(style: :div)}>
61
+ #{render_children}
62
+ </div>
63
+ </body>
64
+ HTML
65
+ end
66
+ end
67
+ end
68
+
69
+ Registry.register(Components::MjBody)
70
+ end
@@ -0,0 +1,198 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../body_component"
4
+ require_relative "../../registry"
5
+ require_relative "../../helpers/width_parser"
6
+
7
+ module Emjay
8
+ module Components
9
+ class MjButton < BodyComponent
10
+ def self.component_name
11
+ "mj-button"
12
+ end
13
+
14
+ def self.ending_tag?
15
+ true
16
+ end
17
+
18
+ def self.default_attributes
19
+ {
20
+ "align" => "center",
21
+ "background-color" => "#414141",
22
+ "border" => "none",
23
+ "border-radius" => "3px",
24
+ "color" => "#ffffff",
25
+ "font-family" => "Ubuntu, Helvetica, Arial, sans-serif",
26
+ "font-size" => "13px",
27
+ "font-weight" => "normal",
28
+ "inner-padding" => "10px 25px",
29
+ "line-height" => "120%",
30
+ "padding" => "10px 25px",
31
+ "target" => "_blank",
32
+ "text-decoration" => "none",
33
+ "text-transform" => "none",
34
+ "vertical-align" => "middle"
35
+ }
36
+ end
37
+
38
+ def self.allowed_attributes
39
+ {
40
+ "align" => "enum(left,center,right)",
41
+ "background-color" => "color",
42
+ "border-bottom" => "string",
43
+ "border-left" => "string",
44
+ "border-radius" => "string",
45
+ "border-right" => "string",
46
+ "border-top" => "string",
47
+ "border" => "string",
48
+ "color" => "color",
49
+ "container-background-color" => "color",
50
+ "font-family" => "string",
51
+ "font-size" => "unit(px)",
52
+ "font-style" => "string",
53
+ "font-weight" => "string",
54
+ "height" => "unit(px,%)",
55
+ "href" => "string",
56
+ "name" => "string",
57
+ "title" => "string",
58
+ "inner-padding" => "unit(px,%){1,4}",
59
+ "letter-spacing" => "unitWithNegative(px,em)",
60
+ "line-height" => "unit(px,%,)",
61
+ "padding-bottom" => "unit(px,%)",
62
+ "padding-left" => "unit(px,%)",
63
+ "padding-right" => "unit(px,%)",
64
+ "padding-top" => "unit(px,%)",
65
+ "padding" => "unit(px,%){1,4}",
66
+ "rel" => "string",
67
+ "target" => "string",
68
+ "text-decoration" => "string",
69
+ "text-transform" => "string",
70
+ "vertical-align" => "enum(top,bottom,middle)",
71
+ "text-align" => "enum(left,right,center)",
72
+ "width" => "unit(px,%)"
73
+ }
74
+ end
75
+
76
+ def get_styles
77
+ {
78
+ table: {
79
+ "border-collapse" => "separate",
80
+ "width" => get_attribute("width"),
81
+ "line-height" => "100%"
82
+ },
83
+ td: {
84
+ "border" => get_attribute("border"),
85
+ "border-bottom" => get_attribute("border-bottom"),
86
+ "border-left" => get_attribute("border-left"),
87
+ "border-radius" => get_attribute("border-radius"),
88
+ "border-right" => get_attribute("border-right"),
89
+ "border-top" => get_attribute("border-top"),
90
+ "cursor" => "auto",
91
+ "font-style" => get_attribute("font-style"),
92
+ "height" => get_attribute("height"),
93
+ "mso-padding-alt" => get_attribute("inner-padding"),
94
+ "text-align" => get_attribute("text-align"),
95
+ "background" => get_attribute("background-color")
96
+ },
97
+ content: {
98
+ "display" => "inline-block",
99
+ "width" => calculate_a_width(get_attribute("width")),
100
+ "background" => get_attribute("background-color"),
101
+ "color" => get_attribute("color"),
102
+ "font-family" => get_attribute("font-family"),
103
+ "font-size" => get_attribute("font-size"),
104
+ "font-style" => get_attribute("font-style"),
105
+ "font-weight" => get_attribute("font-weight"),
106
+ "line-height" => get_attribute("line-height"),
107
+ "letter-spacing" => get_attribute("letter-spacing"),
108
+ "margin" => "0",
109
+ "text-decoration" => get_attribute("text-decoration"),
110
+ "text-transform" => get_attribute("text-transform"),
111
+ "padding" => get_attribute("inner-padding"),
112
+ "mso-padding-alt" => "0px",
113
+ "border-radius" => get_attribute("border-radius")
114
+ }
115
+ }
116
+ end
117
+
118
+ def render
119
+ tag = get_attribute("href") ? "a" : "p"
120
+
121
+ bgcolor = get_attribute("background-color")
122
+ bgcolor_attr = (bgcolor == "none") ? nil : bgcolor
123
+
124
+ tag_attrs = if tag == "a"
125
+ html_attributes(
126
+ href: get_attribute("href"),
127
+ name: get_attribute("name"),
128
+ rel: get_attribute("rel"),
129
+ title: get_attribute("title"),
130
+ style: :content,
131
+ target: get_attribute("target")
132
+ )
133
+ else
134
+ html_attributes(
135
+ href: get_attribute("href"),
136
+ name: get_attribute("name"),
137
+ rel: get_attribute("rel"),
138
+ title: get_attribute("title"),
139
+ style: :content
140
+ )
141
+ end
142
+
143
+ table_attrs = html_attributes(
144
+ border: "0",
145
+ cellpadding: "0",
146
+ cellspacing: "0",
147
+ role: "presentation",
148
+ style: :table
149
+ )
150
+
151
+ td_attrs = html_attributes(
152
+ align: "center",
153
+ bgcolor: bgcolor_attr,
154
+ role: "presentation",
155
+ style: :td,
156
+ valign: get_attribute("vertical-align")
157
+ )
158
+
159
+ <<~HTML
160
+ <table
161
+ #{table_attrs}
162
+ >
163
+ <tbody>
164
+ <tr>
165
+ <td
166
+ #{td_attrs}
167
+ >
168
+ <#{tag}
169
+ #{tag_attrs}
170
+ >
171
+ #{get_content}
172
+ </#{tag}>
173
+ </td>
174
+ </tr>
175
+ </tbody>
176
+ </table>
177
+ HTML
178
+ end
179
+
180
+ private
181
+
182
+ def calculate_a_width(width)
183
+ return nil unless width
184
+
185
+ parsed = WidthParser.call(width)
186
+ return nil unless parsed[:unit] == "px"
187
+
188
+ borders = get_box_widths[:borders]
189
+ inner_paddings = get_shorthand_attr_value("inner-padding", "left") +
190
+ get_shorthand_attr_value("inner-padding", "right")
191
+
192
+ "#{parsed[:parsed_width] - inner_paddings - borders}px"
193
+ end
194
+ end
195
+ end
196
+
197
+ Registry.register(Components::MjButton)
198
+ end