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

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 (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>