bootstrap-email 1.0.0.alpha1.2 → 1.0.0.alpha2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/core/bootstrap-email.scss +2 -0
  4. data/core/bootstrap-head.scss +1 -1
  5. data/core/scss/_selectors_for_utils.scss +24 -0
  6. data/core/scss/_utilities.scss +24 -4
  7. data/core/scss/components/_grid.scss +3 -0
  8. data/core/scss/components/_stack.scss +33 -0
  9. data/core/scss/utilities/_sizing.scss +17 -2
  10. data/core/scss/utilities/_typography.scss +4 -10
  11. data/core/scss/utilities/_valign.scss +5 -0
  12. data/core/templates/body.html.erb +1 -1
  13. data/core/templates/container.html.erb +3 -3
  14. data/core/templates/table-left.html.erb +1 -1
  15. data/core/templates/table-to-tbody.html.erb +5 -0
  16. data/core/templates/{row.html.erb → table-to-tr.html.erb} +1 -1
  17. data/core/templates/table.html.erb +1 -1
  18. data/core/templates/td.html.erb +3 -0
  19. data/core/templates/tr.html.erb +5 -0
  20. data/lib/{bootstrap_email.rb → bootstrap-email.rb} +1 -0
  21. data/lib/bootstrap-email/bootstrap_email_cli.rb +3 -3
  22. data/lib/bootstrap-email/compiler.rb +30 -50
  23. data/lib/bootstrap-email/components/align.rb +7 -6
  24. data/lib/bootstrap-email/components/base.rb +31 -3
  25. data/lib/bootstrap-email/components/block.rb +13 -0
  26. data/lib/bootstrap-email/components/body.rb +2 -14
  27. data/lib/bootstrap-email/components/card.rb +2 -2
  28. data/lib/bootstrap-email/components/force_encoding.rb +16 -0
  29. data/lib/bootstrap-email/components/grid.rb +2 -2
  30. data/lib/bootstrap-email/components/head_style.rb +33 -0
  31. data/lib/bootstrap-email/components/hr.rb +1 -1
  32. data/lib/bootstrap-email/components/paragraph.rb +2 -13
  33. data/lib/bootstrap-email/components/preview_text.rb +18 -0
  34. data/lib/bootstrap-email/components/spacing.rb +4 -5
  35. data/lib/bootstrap-email/components/stack.rb +30 -0
  36. data/lib/bootstrap-email/components/version_comment.rb +15 -0
  37. data/lib/bootstrap-email/erb.rb +9 -0
  38. data/lib/bootstrap-email/rails/action_mailer.rb +1 -0
  39. data/lib/bootstrap-email/sass_cache.rb +42 -22
  40. metadata +20 -9
  41. data/core/templates/col.html.erb +0 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dff167ba86e6d966e96d13e01988af9c06a734b2f7bef9fd9c67dac20ba97d72
4
- data.tar.gz: 3a9c52a5b39f2c6a18c0b4e646eedf24c3051eef56a168d3c8fb18ece7daa92a
3
+ metadata.gz: 759e1882e8d2e275343ff2d3b6c92a358f350f35319c14208c26b7641ef989de
4
+ data.tar.gz: 6fb098dd28e51f2c962aa0c774b4948e1ad0499282d66927755a18539c1aa00e
5
5
  SHA512:
6
- metadata.gz: 8ce16ed9d76b94341b7ee39ac4633133d4dc70fa9d2a4952cdef848c61601006bee71452aff8fa9959e303c7970d3e96072459e1d7708bd6d259be2eb36aa6fa
7
- data.tar.gz: 03b4b339a9c43b802b93e8d71dd8c5465ba0e085fc9a28063e09c75ed127adefa02b8f6f1e56b06b455c776cea23f74566e09706fac5333ba440defb5067cc94
6
+ metadata.gz: d5f19d06827848b6a1cdc3f47ed99e66f46d0465748ac7c46d49654f7815e274c3462028442518b0cf6bd6cfb58e21becbc85204eab5abd45445e1dfe962594b
7
+ data.tar.gz: 4b9716bca2ccc400c22b30e12a6c9b7a03be72d9e595c854d99e92ffdf8d78d4b3dd8061fe914a9e60e3d3868df0e23d7383196a10f75d24514b73ed6ccaef95
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.0.alpha1.2
1
+ 1.0.0.alpha2
@@ -16,6 +16,7 @@
16
16
  @import 'scss/components/image';
17
17
  @import 'scss/components/preview';
18
18
  @import 'scss/components/table';
19
+ @import 'scss/components/stack';
19
20
 
20
21
  @import 'scss/utilities/background';
21
22
  @import 'scss/utilities/border';
@@ -26,3 +27,4 @@
26
27
  @import 'scss/utilities/spacing';
27
28
  @import 'scss/utilities/text-decoration';
28
29
  @import 'scss/utilities/typography';
30
+ @import 'scss/utilities/valign';
@@ -28,7 +28,7 @@ a {
28
28
  text-decoration: none;
29
29
  }
30
30
 
31
- // Inhere text color so color can be set on the body and inherited by children
31
+ // Inherit text color so color can be set on the body and inherited by children
32
32
  * {
33
33
  color: inherit;
34
34
  }
@@ -98,3 +98,27 @@
98
98
  }
99
99
  }
100
100
  }
101
+
102
+ //
103
+ // Text align
104
+ //
105
+ @mixin text-align-util($class) {
106
+ #{$class} {
107
+ &,
108
+ & > tbody > tr > td {
109
+ @content
110
+ }
111
+ }
112
+ }
113
+
114
+ //
115
+ // Vertical align
116
+ //
117
+ @mixin valign-util($class) {
118
+ #{$class} {
119
+ &,
120
+ & > tbody > tr > td {
121
+ @content
122
+ }
123
+ }
124
+ }
@@ -117,9 +117,29 @@ $border-widths: (
117
117
  ) !default;
118
118
 
119
119
  $border-radiuses: (
120
- '-sm': 3px,
121
- '': 6px,
122
- '-lg': 9px,
120
+ '-none': 0px,
121
+ '-sm': 2px,
122
+ '': 4px,
123
+ '-md': 6px,
124
+ '-lg': 8px,
123
125
  '-xl': 12px,
124
- '-0': 0px
126
+ '-2xl': 16px,
127
+ '-3xl': 24px,
128
+ '-full': 9999px
129
+ ) !default;
130
+
131
+ $vertical-align: (
132
+ top,
133
+ middle,
134
+ bottom,
135
+ baseline,
136
+ text-top,
137
+ text-bottom
138
+ ) !default;
139
+
140
+ $text-align: (
141
+ left,
142
+ right,
143
+ center,
144
+ justify
125
145
  ) !default;
@@ -1,10 +1,13 @@
1
1
  table.row {
2
2
  table-layout: fixed;
3
+ -premailer-width: 100%;
3
4
  width: 100%;
4
5
  & > tbody > tr > td {
5
6
  min-height: 1px;
6
7
  font-weight: normal;
7
8
  padding-right: 30px;
9
+ vertical-align: top;
10
+ text-align: left;
8
11
  }
9
12
  }
10
13
 
@@ -0,0 +1,33 @@
1
+ @each $space, $value in $spacers {
2
+ table.stack-x.gap-#{$space} > tbody > tr > td {
3
+ padding-right: $value;
4
+ &:last-child {
5
+ padding-right: 0;
6
+ }
7
+ }
8
+ table.stack-y.gap-#{$space} > tbody > tr {
9
+ & > td {
10
+ padding-bottom: $value;
11
+ }
12
+ &:last-child > td {
13
+ padding-bottom: 0;
14
+ }
15
+ }
16
+ }
17
+
18
+ .stack-cell {
19
+ vertical-align: top;
20
+ text-align: left;
21
+ }
22
+
23
+ @each $align in $vertical-align {
24
+ table.stack-valign-#{$align} > tbody > tr > td {
25
+ vertical-align: $align;
26
+ }
27
+ }
28
+
29
+ @each $align in $text-align {
30
+ table.stack-align-#{$align} > tbody > tr > td {
31
+ text-align: $align;
32
+ }
33
+ }
@@ -1,12 +1,12 @@
1
1
  @each $prefix in $breakpoints {
2
2
  @each $name, $property in $sizing-types {
3
- @include sizing-util('.#{$name}-#{$prefix}full') {
3
+ @include sizing-util('.max-#{$name}-#{$prefix}full') {
4
4
  max-#{$property}: 100%;
5
5
  -premailer-#{$property}: 100%;
6
6
  #{$property}: 100%;
7
7
  }
8
8
  @each $size, $value in $sizing {
9
- @include sizing-util('.#{$name}-#{$prefix}#{$size}') {
9
+ @include sizing-util('.max-#{$name}-#{$prefix}#{$size}') {
10
10
  max-#{$property}: $value;
11
11
  -premailer-#{$property}: strip-unit($value);
12
12
  #{$property}: 100%;
@@ -14,3 +14,18 @@
14
14
  }
15
15
  }
16
16
  }
17
+
18
+ @each $prefix in $breakpoints {
19
+ @each $name, $property in $sizing-types {
20
+ @include sizing-util('.#{$name}-#{$prefix}full') {
21
+ -premailer-#{$property}: 100%;
22
+ #{$property}: 100%;
23
+ }
24
+ @each $size, $value in $sizing {
25
+ @include sizing-util('.#{$name}-#{$prefix}#{$size}') {
26
+ -premailer-#{$property}: strip-unit($value);
27
+ #{$property}: $value;
28
+ }
29
+ }
30
+ }
31
+ }
@@ -36,16 +36,10 @@ h1, h2, h3, h4, h5, h6,
36
36
  }
37
37
  }
38
38
 
39
- .text-left {
40
- text-align: left !important;
41
- }
42
-
43
- .text-right {
44
- text-align: right !important;
45
- }
46
-
47
- .text-center {
48
- text-align: center !important;
39
+ @each $align in $text-align {
40
+ @include text-align-util('.text-#{$align}') {
41
+ text-align: $align !important;
42
+ }
49
43
  }
50
44
 
51
45
  p {
@@ -0,0 +1,5 @@
1
+ @each $align in $vertical-align {
2
+ @include valign-util('.valign-#{$align}') {
3
+ vertical-align: $align !important;
4
+ }
5
+ }
@@ -1,4 +1,4 @@
1
- <table valign="top" class="<%= classes %>">
1
+ <table class="<%= classes %>" valign="top" role="presentation">
2
2
  <tbody>
3
3
  <tr>
4
4
  <td valign="top">
@@ -1,14 +1,14 @@
1
- <table class="<%= classes %>">
1
+ <table class="<%= classes %>" role="presentation">
2
2
  <tbody>
3
3
  <tr>
4
4
  <td align="center">
5
5
  <!--[if (gte mso 9)|(IE)]>
6
- <table align="center">
6
+ <table align="center" role="presentation">
7
7
  <tbody>
8
8
  <tr>
9
9
  <td width="600">
10
10
  <![endif]-->
11
- <table align="center">
11
+ <table align="center" role="presentation">
12
12
  <tbody>
13
13
  <tr>
14
14
  <td>
@@ -1,4 +1,4 @@
1
- <table class="<%= classes %>" align="left">
1
+ <table class="<%= classes %>" align="left" role="presentation">
2
2
  <tbody>
3
3
  <tr>
4
4
  <td>
@@ -0,0 +1,5 @@
1
+ <table class="<%= classes %>" role="presentation">
2
+ <tbody>
3
+ <%= contents %>
4
+ </tbody>
5
+ </table>
@@ -1,4 +1,4 @@
1
- <table class="<%= classes %>">
1
+ <table class="<%= classes %>" role="presentation">
2
2
  <tbody>
3
3
  <tr>
4
4
  <%= contents %>
@@ -1,4 +1,4 @@
1
- <table class="<%= classes %>">
1
+ <table class="<%= classes %>" role="presentation">
2
2
  <tbody>
3
3
  <tr>
4
4
  <td>
@@ -0,0 +1,3 @@
1
+ <td class="<%= classes %>">
2
+ <%= contents %>
3
+ </td>
@@ -0,0 +1,5 @@
1
+ <tr>
2
+ <td>
3
+ <%= contents %>
4
+ </td>
5
+ </tr>
@@ -15,6 +15,7 @@ if defined?(Rails)
15
15
  end
16
16
 
17
17
  require_relative 'bootstrap-email/initialize'
18
+ require_relative 'bootstrap-email/erb'
18
19
  require_relative 'bootstrap-email/compiler'
19
20
  require_relative 'bootstrap-email/sass_cache'
20
21
  require_relative 'bootstrap-email/version'
@@ -1,10 +1,10 @@
1
- require_relative '../bootstrap_email'
1
+ require_relative '../bootstrap-email'
2
2
  require 'optparse'
3
3
  require 'fileutils'
4
4
 
5
5
  input = nil
6
6
  options = {
7
- destination: 'output',
7
+ destination: 'compiled',
8
8
  type: :file
9
9
  }
10
10
 
@@ -17,7 +17,7 @@ parser = OptionParser.new do |opts|
17
17
  opts.separator ' bootstrap-email email.html > out.html'
18
18
  opts.separator ' bootstrap-email ./public/index.html'
19
19
  opts.separator ' bootstrap-email -s \'<a href="#" class="btn btn-primary">Some Button</a>\''
20
- opts.separator ' bootstrap-email -p \'emails/*\' -d emails/output'
20
+ opts.separator ' bootstrap-email -p \'emails/*\' -d emails/compiled'
21
21
  opts.separator ' bootstrap-email -p \'views/emails/*\' -d \'views/compiled_emails\''
22
22
  opts.separator ' cat input.html | bootstrap-email'
23
23
  opts.separator ''
@@ -13,21 +13,30 @@ module BootstrapEmail
13
13
  when :file
14
14
  html = File.read(input)
15
15
  end
16
+ html = add_layout!(html)
16
17
  build_premailer_doc(html, options)
17
18
  end
18
19
 
19
20
  def perform_full_compile
20
21
  compile_html!
21
22
  inline_css!
22
- inject_head!
23
- inject_comment!
23
+ configure_html!
24
24
  finalize_document!
25
25
  end
26
26
 
27
27
  private
28
28
 
29
+ def add_layout!(html)
30
+ document = Nokogiri::HTML(html)
31
+ return html unless document.at_css('head').nil?
32
+
33
+ BootstrapEmail::Erb.template(
34
+ File.expand_path('../../core/layout.html.erb', __dir__),
35
+ contents: html
36
+ )
37
+ end
38
+
29
39
  def build_premailer_doc(html, options)
30
- html = add_layout!(html)
31
40
  SassC.load_paths << File.expand_path('../../core', __dir__)
32
41
  css_string = BootstrapEmail::SassCache.compile('bootstrap-email', config_path: options[:config_path], style: :expanded)
33
42
  self.premailer = Premailer.new(
@@ -39,80 +48,51 @@ module BootstrapEmail
39
48
  self.doc = premailer.doc
40
49
  end
41
50
 
42
- def add_layout!(html)
43
- document = Nokogiri::HTML(html)
44
- return document.to_html unless document.at_css('head').nil?
45
-
46
- namespace = OpenStruct.new(contents: ERB.new(document.to_html).result)
47
- template_html = File.read(File.expand_path('../../core/layout.html.erb', __dir__))
48
- ERB.new(template_html).result(namespace.instance_eval { binding })
49
- end
50
-
51
51
  def compile_html!
52
+ BootstrapEmail::Component::Block.build(doc)
53
+
52
54
  BootstrapEmail::Component::Button.build(doc)
53
55
  BootstrapEmail::Component::Badge.build(doc)
54
56
  BootstrapEmail::Component::Alert.build(doc)
55
57
  BootstrapEmail::Component::Card.build(doc)
56
- BootstrapEmail::Component::Paragraph.build(doc)
58
+ # BootstrapEmail::Component::Paragraph.build(doc) this might be too much
57
59
  BootstrapEmail::Component::Hr.build(doc)
58
60
  BootstrapEmail::Component::Container.build(doc)
59
61
  BootstrapEmail::Component::Grid.build(doc)
60
- BootstrapEmail::Component::Align.build(doc)
61
- BootstrapEmail::Component::Color.build(doc)
62
+ BootstrapEmail::Component::Stack.build(doc)
63
+
64
+ BootstrapEmail::Component::Spacing.build(doc)
62
65
  BootstrapEmail::Component::Padding.build(doc)
63
66
  BootstrapEmail::Component::Margin.build(doc)
64
- BootstrapEmail::Component::Spacing.build(doc)
65
67
  BootstrapEmail::Component::Spacer.build(doc)
68
+
66
69
  BootstrapEmail::Component::Table.build(doc)
67
70
  BootstrapEmail::Component::Body.build(doc)
71
+ BootstrapEmail::Component::Align.build(doc)
72
+ BootstrapEmail::Component::Color.build(doc)
73
+
74
+ BootstrapEmail::Component::PreviewText.build(doc)
68
75
  end
69
76
 
70
77
  def inline_css!
71
78
  premailer.to_inline_css
72
79
  end
73
80
 
74
- def inject_head!
75
- doc.at_css('head').add_child(bootstrap_email_head)
76
- end
77
-
78
- def inject_comment!
79
- doc.at_css('head').prepend_child(bootstrap_email_comment)
81
+ def configure_html!
82
+ BootstrapEmail::Component::ForceEncoding.build(doc)
83
+ BootstrapEmail::Component::HeadStyle.build(doc)
84
+ BootstrapEmail::Component::VersionComment.build(doc)
80
85
  end
81
86
 
82
87
  def finalize_document!
88
+ html = BootstrapEmail::Component::ForceEncoding.replace(doc.to_html)
83
89
  case type
84
90
  when :rails
85
- (@mail.html_part || @mail).body = doc.to_html
91
+ (@mail.html_part || @mail).body = html
86
92
  @mail
87
93
  when :string, :file
88
- doc.to_html
89
- end
90
- end
91
-
92
- def bootstrap_email_head
93
- html_string = <<-INLINE
94
- <style type="text/css">
95
- #{purged_css_from_head}
96
- </style>
97
- INLINE
98
- html_string
99
- end
100
-
101
- def bootstrap_email_comment
102
- "\n <!-- Compiled with Bootstrap Email version: #{BootstrapEmail::VERSION} -->"
103
- end
104
-
105
- def purged_css_from_head
106
- default, custom = BootstrapEmail::SassCache.compile('bootstrap-head').split('/*! allow_purge_after */')
107
- # get each CSS declaration
108
- custom.scan(/\w*\.[\w\-]*[\s\S\n]+?(?=})}{1}/).each do |group|
109
- # get the first class for each comma separated CSS declaration
110
- exist = group.scan(/(\.[\w\-]*).*?((,+?)|{+?)/).map(&:first).uniq.any? do |selector|
111
- !doc.at_css(selector).nil?
112
- end
113
- custom.sub!(group, '') unless exist
94
+ html
114
95
  end
115
- (default + custom).gsub(/\n\s*\n+/, "\n")
116
96
  end
117
97
  end
118
98
  end
@@ -3,16 +3,17 @@ module BootstrapEmail
3
3
  class Align < Base
4
4
  def build
5
5
  ['left', 'center', 'right'].each do |type|
6
- each_node(".align-#{type}") do |node|
7
- align_helper(node, type)
6
+ full_type = "align-#{type}"
7
+ each_node(".#{full_type}") do |node|
8
+ align_helper(node, full_type, type)
8
9
  end
9
10
  end
10
11
  end
11
12
 
12
- def align_helper(node, type)
13
- if node.name != 'table'
14
- node['class'] = node['class'].sub("align-#{type}", '')
15
- node = node.replace(template('table', classes: "align-#{type}", contents: node.to_html))[0]
13
+ def align_helper(node, full_type, type)
14
+ unless table?(node) || td?(node)
15
+ node['class'] = node['class'].sub(full_type, '')
16
+ node = node.replace(template('table', classes: full_type, contents: node.to_html))[0]
16
17
  end
17
18
  node['align'] = type
18
19
  end
@@ -10,17 +10,45 @@ module BootstrapEmail
10
10
  new(doc).build
11
11
  end
12
12
 
13
+ private
14
+
13
15
  def template(file, locals_hash = {})
14
16
  locals_hash[:classes] = locals_hash[:classes].split.join(' ') if locals_hash[:classes]
15
- namespace = OpenStruct.new(locals_hash)
16
- template_html = File.read(File.expand_path("../../../core/templates/#{file}.html.erb", __dir__))
17
- ERB.new(template_html).result(namespace.instance_eval { binding })
17
+ BootstrapEmail::Erb.template(
18
+ File.expand_path("../../../core/templates/#{file}.html.erb", __dir__),
19
+ locals_hash
20
+ )
18
21
  end
19
22
 
20
23
  def each_node(css_lookup, &blk)
21
24
  # sort by youngest child and traverse backwards up the tree
22
25
  doc.css(css_lookup).sort_by { |n| n.ancestors.size }.reverse!.each(&blk)
23
26
  end
27
+
28
+ def add_class(node, class_name)
29
+ node['class'] ||= ''
30
+ node['class'] += class_name
31
+ end
32
+
33
+ def margin?(node)
34
+ margin_top?(node) || margin_bottom?(node)
35
+ end
36
+
37
+ def margin_top?(node)
38
+ node['class'].to_s.match?(/m[ty]{1}-(lg-)?\d+/)
39
+ end
40
+
41
+ def margin_bottom?(node)
42
+ node['class'].to_s.match?(/m[by]{1}-(lg-)?\d+/)
43
+ end
44
+
45
+ def table?(node)
46
+ node.name == 'table'
47
+ end
48
+
49
+ def td?(node)
50
+ node.name == 'td'
51
+ end
24
52
  end
25
53
  end
26
54
  end
@@ -0,0 +1,13 @@
1
+ module BootstrapEmail
2
+ module Component
3
+ class Block < Base
4
+ def build
5
+ each_node('block, .to-table') do |node|
6
+ # add .to-table if it's not already there
7
+ class_name = node['class'].to_s.split << 'to-table'
8
+ node.replace(template('table', classes: class_name.uniq.join(' '), contents: node.inner_html))
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -2,20 +2,8 @@ module BootstrapEmail
2
2
  module Component
3
3
  class Body < Base
4
4
  def build
5
- each_node('body') do |node|
6
- node.replace('<body>' + preview_text.to_s + template('body', classes: "#{node['class']} body", contents: node.inner_html) + '</body>')
7
- end
8
- end
9
-
10
- def preview_text
11
- preview_node = doc.at_css('preview')
12
- return if preview_node.nil?
13
-
14
- # apply spacing after the text max of 100 characters so it doesn't show body text
15
- preview_node.content += '&nbsp;' * [(100 - preview_node.content.length.to_i), 0].max
16
- node = template('div', classes: 'preview', contents: preview_node.content)
17
- preview_node.remove
18
- node
5
+ body = doc.at_css('body')
6
+ body.inner_html = template('body', classes: "#{body['class']} body", contents: body.inner_html)
19
7
  end
20
8
  end
21
9
  end
@@ -3,10 +3,10 @@ module BootstrapEmail
3
3
  class Card < Base
4
4
  def build
5
5
  each_node('.card') do |node|
6
- node.replace(template('table', classes: node['class'], contents: node.delete('class') && node.inner_html))
6
+ node.replace(template('table', classes: node['class'], contents: node.inner_html))
7
7
  end
8
8
  each_node('.card-body') do |node|
9
- node.replace(template('table', classes: node['class'], contents: node.delete('class') && node.inner_html))
9
+ node.replace(template('table', classes: node['class'], contents: node.inner_html))
10
10
  end
11
11
  end
12
12
  end
@@ -0,0 +1,16 @@
1
+ module BootstrapEmail
2
+ module Component
3
+ class ForceEncoding < Base
4
+ def build
5
+ body = doc.at_css('body')
6
+ body.add_child('<force-encoding></force-encoding>')
7
+ end
8
+
9
+ def self.replace(html)
10
+ # force utf-8 character encoded in iOS Mail: https://github.com/bootstrap-email/bootstrap-email/issues/50
11
+ # this needs to be done after the document has been outputted to a string so it doesn't get converted
12
+ html.sub('<force-encoding></force-encoding>', '<div id="force-encoding-to-utf-8" style="display: none;">&#10175;</div>')
13
+ end
14
+ end
15
+ end
16
+ end
@@ -3,10 +3,10 @@ module BootstrapEmail
3
3
  class Grid < Base
4
4
  def build
5
5
  each_node('.row') do |node|
6
- node.replace(template('row', classes: node['class'], contents: node.inner_html))
6
+ node.replace(template('table-to-tr', classes: node['class'], contents: node.inner_html))
7
7
  end
8
8
  each_node('*[class*=col]') do |node|
9
- node.replace(template('col', classes: node['class'], contents: node.inner_html))
9
+ node.replace(template('td', classes: node['class'], contents: node.inner_html))
10
10
  end
11
11
  end
12
12
  end
@@ -0,0 +1,33 @@
1
+ module BootstrapEmail
2
+ module Component
3
+ class HeadStyle < Base
4
+ def build
5
+ doc.at_css('head').add_child(bootstrap_email_head)
6
+ end
7
+
8
+ private
9
+
10
+ def bootstrap_email_head
11
+ html_string = <<-INLINE
12
+ <style type="text/css">
13
+ #{purged_css_from_head}
14
+ </style>
15
+ INLINE
16
+ html_string
17
+ end
18
+
19
+ def purged_css_from_head
20
+ default, custom = BootstrapEmail::SassCache.compile('bootstrap-head').split('/*! allow_purge_after */')
21
+ # get each CSS declaration
22
+ custom.scan(/\w*\.[\w\-]*[\s\S\n]+?(?=})}{1}/).each do |group|
23
+ # get the first class for each comma separated CSS declaration
24
+ exist = group.scan(/(\.[\w\-]*).*?((,+?)|{+?)/).map(&:first).uniq.any? do |selector|
25
+ !doc.at_css(selector).nil?
26
+ end
27
+ custom.sub!(group, '') unless exist
28
+ end
29
+ (default + custom).gsub(/\n\s*\n+/, "\n")
30
+ end
31
+ end
32
+ end
33
+ end
@@ -3,7 +3,7 @@ module BootstrapEmail
3
3
  class Hr < Base
4
4
  def build
5
5
  each_node('hr') do |node|
6
- default_margin = node['class'].to_s.match?(/m[tby]{1}-(lg-)?\d+/) ? '' : 'my-5'
6
+ default_margin = margin?(node) ? '' : 'my-5'
7
7
  node.replace(template('table', classes: "#{default_margin} hr #{node['class']}", contents: ''))
8
8
  end
9
9
  end
@@ -3,22 +3,11 @@ module BootstrapEmail
3
3
  class Paragraph < Base
4
4
  def build
5
5
  each_node('p') do |node|
6
- next if margin?(node) || space_y?(node)
6
+ next if margin?(node)
7
7
 
8
- node['class'] ||= ''
9
- node['class'] += 'mb-4'
8
+ add_class(node, 'mb-4')
10
9
  end
11
10
  end
12
-
13
- private
14
-
15
- def margin?(node)
16
- node['class'].to_s.match?(/m[tby]{1}-(lg-)?\d+/)
17
- end
18
-
19
- def space_y?(node)
20
- node.parent['class'].to_s.match?(/space-y-(lg-)?\d+/)
21
- end
22
11
  end
23
12
  end
24
13
  end
@@ -0,0 +1,18 @@
1
+ module BootstrapEmail
2
+ module Component
3
+ class PreviewText < Base
4
+ def build
5
+ preview_node = doc.at_css('preview')
6
+ return if preview_node.nil?
7
+
8
+ # apply spacing after the text max of 100 characters so it doesn't show body text
9
+ preview_node.inner_html += '&nbsp;' * [(100 - preview_node.content.length.to_i), 0].max
10
+ node = template('div', classes: 'preview', contents: preview_node.content)
11
+ preview_node.remove
12
+
13
+ body = doc.at_css('body')
14
+ body.prepend_child(node)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -5,11 +5,10 @@ module BootstrapEmail
5
5
  each_node('*[class*=space-y-]') do |node|
6
6
  spacer = node['class'].scan(/space-y-((lg-)?\d+)/)[0][0]
7
7
  # get all direct children except the first
8
- node.xpath('./*[position()>1] | ./tbody/tr/td/*[position()>1]').each do |child|
9
- html = ''
10
- html += template('div', classes: "s-#{spacer}", contents: nil)
11
- html += child.to_html
12
- child.replace(html)
8
+ node.xpath('./*[position() < last()] | ./tbody/tr/td/*[position() < last()]').each do |child|
9
+ next if margin_bottom?(child)
10
+
11
+ add_class(child, "mb-#{spacer}")
13
12
  end
14
13
  end
15
14
  end
@@ -0,0 +1,30 @@
1
+ module BootstrapEmail
2
+ module Component
3
+ class Stack < Base
4
+ def build
5
+ stack_x
6
+ stack_y
7
+ end
8
+
9
+ def stack_x
10
+ each_node('.stack-x') do |node|
11
+ html = ''
12
+ node.xpath('./*').each do |child|
13
+ html += template('td', classes: 'stack-cell', contents: child.to_html)
14
+ end
15
+ node.replace(template('table-to-tr', classes: node['class'], contents: html))
16
+ end
17
+ end
18
+
19
+ def stack_y
20
+ each_node('.stack-y') do |node|
21
+ html = ''
22
+ node.xpath('./*').each do |child|
23
+ html += template('tr', classes: 'stack-cell', contents: child.to_html)
24
+ end
25
+ node.replace(template('table-to-tbody', classes: node['class'], contents: html))
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,15 @@
1
+ module BootstrapEmail
2
+ module Component
3
+ class VersionComment < Base
4
+ def build
5
+ doc.at_css('head').prepend_child(bootstrap_email_comment)
6
+ end
7
+
8
+ private
9
+
10
+ def bootstrap_email_comment
11
+ "\n <!-- Compiled with Bootstrap Email version: #{BootstrapEmail::VERSION} -->"
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,9 @@
1
+ module BootstrapEmail
2
+ class Erb
3
+ def self.template(path, locals_hash = {})
4
+ namespace = OpenStruct.new(locals_hash)
5
+ template_html = File.read(path)
6
+ ERB.new(template_html).result(namespace.instance_eval { binding })
7
+ end
8
+ end
9
+ end
@@ -5,6 +5,7 @@ ActiveSupport.on_load(:action_mailer, {yield: true}) do |action_mailer|
5
5
  bootstrap = BootstrapEmail::Compiler.new(mail(*args, &block), type: :rails)
6
6
  bootstrap.perform_full_compile
7
7
  end
8
+ alias bootstrap_email bootstrap_mail
8
9
  alias make_bootstrap_mail bootstrap_mail
9
10
  end
10
11
  end
@@ -3,36 +3,47 @@ module BootstrapEmail
3
3
  CACHE_DIR = File.expand_path('../../.sass-cache', __dir__)
4
4
  SASS_DIR = File.expand_path('../../core', __dir__)
5
5
 
6
- def self.compile(name, config_path: nil, style: :compressed)
7
- path = "#{SASS_DIR}/#{name}"
8
- config_file = nil
6
+ def self.compile(type, config_path: nil, style: :compressed)
7
+ new(type, config_path, style).compile
8
+ end
9
9
 
10
- lookup_locations = ["#{name}.config.scss", "app/assets/stylesheets/#{name}.config.scss"]
11
- locations = lookup_locations.select { |location| File.exist?(File.expand_path(location, Dir.pwd)) }
10
+ attr_accessor :type, :style, :file_path, :config_file, :checksum
11
+
12
+ def initialize(type, config_path, style)
13
+ self.type = type
14
+ self.style = style
15
+ self.file_path = "#{SASS_DIR}/#{type}"
16
+ self.config_file = load_config(config_path)
17
+ self.checksum = checksum_files
18
+ end
19
+
20
+ def compile
21
+ cache_path = "#{CACHE_DIR}/#{checksum}/#{type}.css"
22
+ unless cached?(cache_path)
23
+ compile_and_cache_scss(cache_path)
24
+ end
25
+ File.read(cache_path)
26
+ end
12
27
 
28
+ private
29
+
30
+ def load_config(config_path)
31
+ lookup_locations = ["#{type}.config.scss", "app/assets/stylesheets/#{type}.config.scss"]
32
+ locations = lookup_locations.select { |location| File.exist?(File.expand_path(location, Dir.pwd)) }
13
33
  if config_path && File.exist?(config_path)
14
34
  # check if custom config was passed in
15
- config_file = File.read(config_path)
35
+ replace_config(File.read(config_path))
16
36
  elsif locations.any?
17
- config_file = File.read(File.expand_path(locations.first, Dir.pwd))
37
+ # look for common lookup locations of config
38
+ replace_config(File.read(File.expand_path(locations.first, Dir.pwd)))
18
39
  end
40
+ end
19
41
 
20
- check = checksum(config_file&.gsub("//= @import #{name};", "@import '#{path}';"))
21
- cache_path = "#{CACHE_DIR}/#{check}/#{name}.css"
22
- if cached?(cache_path)
23
- File.read(cache_path)
24
- else
25
- file = config_file.nil? ? File.read("#{path}.scss") : config_file
26
- SassC::Engine.new(file, style: style).render.tap do |css|
27
- Dir.mkdir(CACHE_DIR) unless File.directory?(CACHE_DIR)
28
- Dir.mkdir("#{CACHE_DIR}/#{check}") unless File.directory?("#{CACHE_DIR}/#{check}")
29
- File.write(cache_path, css)
30
- puts "New css file cached for #{name}"
31
- end
32
- end
42
+ def replace_config(config_file)
43
+ config_file.gsub("//= @import #{type};", "@import '#{file_path}';")
33
44
  end
34
45
 
35
- def self.checksum(config_file)
46
+ def checksum_files
36
47
  checksums = config_file.nil? ? [] : [Digest::SHA1.hexdigest(config_file)]
37
48
  Dir.glob('../../core/**/*.scss', base: __dir__).each do |path|
38
49
  checksums << Digest::SHA1.file(File.expand_path(path, __dir__)).hexdigest
@@ -40,8 +51,17 @@ module BootstrapEmail
40
51
  Digest::SHA1.hexdigest(checksums.join)
41
52
  end
42
53
 
43
- def self.cached?(cache_path)
54
+ def cached?(cache_path)
44
55
  File.file?(cache_path)
45
56
  end
57
+
58
+ def compile_and_cache_scss(cache_path)
59
+ file = config_file || File.read("#{file_path}.scss")
60
+ css = SassC::Engine.new(file, style: style).render
61
+ Dir.mkdir(CACHE_DIR) unless File.directory?(CACHE_DIR)
62
+ Dir.mkdir("#{CACHE_DIR}/#{checksum}") unless File.directory?("#{CACHE_DIR}/#{checksum}")
63
+ File.write(cache_path, css)
64
+ puts "New css file cached for #{type}"
65
+ end
46
66
  end
47
67
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bootstrap-email
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.alpha1.2
4
+ version: 1.0.0.alpha2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stuart Yamartino
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-01-26 00:00:00.000000000 Z
11
+ date: 2021-03-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -30,28 +30,28 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '1.14'
33
+ version: '1.7'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '1.14'
40
+ version: '1.7'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: sassc
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '2.4'
47
+ version: '2.0'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '2.4'
54
+ version: '2.0'
55
55
  description:
56
56
  email: stu@stuyam.com
57
57
  executables:
@@ -80,6 +80,7 @@ files:
80
80
  - core/scss/components/_hr.scss
81
81
  - core/scss/components/_image.scss
82
82
  - core/scss/components/_preview.scss
83
+ - core/scss/components/_stack.scss
83
84
  - core/scss/components/_table.scss
84
85
  - core/scss/utilities/_background.scss
85
86
  - core/scss/utilities/_border-radius.scss
@@ -90,38 +91,48 @@ files:
90
91
  - core/scss/utilities/_spacing.scss
91
92
  - core/scss/utilities/_text-decoration.scss
92
93
  - core/scss/utilities/_typography.scss
94
+ - core/scss/utilities/_valign.scss
93
95
  - core/templates/body.html.erb
94
- - core/templates/col.html.erb
95
96
  - core/templates/container.html.erb
96
97
  - core/templates/div.html.erb
97
- - core/templates/row.html.erb
98
98
  - core/templates/table-left.html.erb
99
+ - core/templates/table-to-tbody.html.erb
100
+ - core/templates/table-to-tr.html.erb
99
101
  - core/templates/table.html.erb
102
+ - core/templates/td.html.erb
103
+ - core/templates/tr.html.erb
104
+ - lib/bootstrap-email.rb
100
105
  - lib/bootstrap-email/bootstrap_email_cli.rb
101
106
  - lib/bootstrap-email/compiler.rb
102
107
  - lib/bootstrap-email/components/alert.rb
103
108
  - lib/bootstrap-email/components/align.rb
104
109
  - lib/bootstrap-email/components/badge.rb
105
110
  - lib/bootstrap-email/components/base.rb
111
+ - lib/bootstrap-email/components/block.rb
106
112
  - lib/bootstrap-email/components/body.rb
107
113
  - lib/bootstrap-email/components/button.rb
108
114
  - lib/bootstrap-email/components/card.rb
109
115
  - lib/bootstrap-email/components/color.rb
110
116
  - lib/bootstrap-email/components/container.rb
117
+ - lib/bootstrap-email/components/force_encoding.rb
111
118
  - lib/bootstrap-email/components/grid.rb
119
+ - lib/bootstrap-email/components/head_style.rb
112
120
  - lib/bootstrap-email/components/hr.rb
113
121
  - lib/bootstrap-email/components/margin.rb
114
122
  - lib/bootstrap-email/components/padding.rb
115
123
  - lib/bootstrap-email/components/paragraph.rb
124
+ - lib/bootstrap-email/components/preview_text.rb
116
125
  - lib/bootstrap-email/components/spacer.rb
117
126
  - lib/bootstrap-email/components/spacing.rb
127
+ - lib/bootstrap-email/components/stack.rb
118
128
  - lib/bootstrap-email/components/table.rb
129
+ - lib/bootstrap-email/components/version_comment.rb
130
+ - lib/bootstrap-email/erb.rb
119
131
  - lib/bootstrap-email/initialize.rb
120
132
  - lib/bootstrap-email/rails/action_mailer.rb
121
133
  - lib/bootstrap-email/rails/engine.rb
122
134
  - lib/bootstrap-email/sass_cache.rb
123
135
  - lib/bootstrap-email/version.rb
124
- - lib/bootstrap_email.rb
125
136
  homepage: https://bootstrapemail.com
126
137
  licenses:
127
138
  - MIT
@@ -1,3 +0,0 @@
1
- <td class="<%= classes %>" align="left" valign="top">
2
- <%= contents %>
3
- </td>